import BoundingRectangle from '../Core/BoundingRectangle.js';
import BoundingSphere from '../Core/BoundingSphere.js';
import BoxGeometry from '../Core/BoxGeometry.js';
import Cartesian3 from '../Core/Cartesian3.js';
import Cartesian4 from '../Core/Cartesian4.js';
import Cartographic from '../Core/Cartographic.js';
import clone from '../Core/clone.js';
import Color from '../Core/Color.js';
import ColorGeometryInstanceAttribute from '../Core/ColorGeometryInstanceAttribute.js';
import createGuid from '../Core/createGuid.js';
import CullingVolume from '../Core/CullingVolume.js';
import defaultValue from '../Core/defaultValue.js';
import defined from '../Core/defined.js';
import deprecationWarning from '../Core/deprecationWarning.js';
import destroyObject from '../Core/destroyObject.js';
import DeveloperError from '../Core/DeveloperError.js';
import EllipsoidGeometry from '../Core/EllipsoidGeometry.js';
import Event from '../Core/Event.js';
import GeographicProjection from '../Core/GeographicProjection.js';
import GeometryInstance from '../Core/GeometryInstance.js';
import GeometryPipeline from '../Core/GeometryPipeline.js';
import Intersect from '../Core/Intersect.js';
import JulianDate from '../Core/JulianDate.js';
import CesiumMath from '../Core/Math.js';
import Matrix4 from '../Core/Matrix4.js';
import mergeSort from '../Core/mergeSort.js';
import Occluder from '../Core/Occluder.js';
import OrthographicFrustum from '../Core/OrthographicFrustum.js';
import OrthographicOffCenterFrustum from '../Core/OrthographicOffCenterFrustum.js';
import PerspectiveFrustum from '../Core/PerspectiveFrustum.js';
import PerspectiveOffCenterFrustum from '../Core/PerspectiveOffCenterFrustum.js';
import RequestScheduler from '../Core/RequestScheduler.js';
import TaskProcessor from '../Core/TaskProcessor.js';
import Transforms from '../Core/Transforms.js';
import ClearCommand from '../Renderer/ClearCommand.js';
import ComputeEngine from '../Renderer/ComputeEngine.js';
import Context from '../Renderer/Context.js';
import ContextLimits from '../Renderer/ContextLimits.js';
import DrawCommand from '../Renderer/DrawCommand.js';
import Pass from '../Renderer/Pass.js';
import RenderState from '../Renderer/RenderState.js';
import ShaderProgram from '../Renderer/ShaderProgram.js';
import ShaderSource from '../Renderer/ShaderSource.js';
import BrdfLutGenerator from './BrdfLutGenerator.js';
import Camera from './Camera.js';
import Cesium3DTilePass from './Cesium3DTilePass.js';
import Cesium3DTilePassState from './Cesium3DTilePassState.js';
import CreditDisplay from './CreditDisplay.js';
import DebugCameraPrimitive from './DebugCameraPrimitive.js';
import DepthPlane from './DepthPlane.js';
import DerivedCommand from './DerivedCommand.js';
import DeviceOrientationCameraController from './DeviceOrientationCameraController.js';
import Fog from './Fog.js';
import FrameState from './FrameState.js';
import GlobeDepth from './GlobeDepth.js';
import InvertClassification from './InvertClassification.js';
import JobScheduler from './JobScheduler.js';
import MapMode2D from './MapMode2D.js';
import OctahedralProjectedCubeMap from './OctahedralProjectedCubeMap.js';
import PerformanceDisplay from './PerformanceDisplay.js';
import PerInstanceColorAppearance from './PerInstanceColorAppearance.js';
import Picking from './Picking.js';
import PostProcessStageCollection from './PostProcessStageCollection.js';
import Primitive from './Primitive.js';
import PrimitiveCollection from './PrimitiveCollection.js';
import SceneMode from './SceneMode.js';
import SceneTransforms from './SceneTransforms.js';
import SceneTransitioner from './SceneTransitioner.js';
import ScreenSpaceCameraController from './ScreenSpaceCameraController.js';
import ShadowMap from './ShadowMap.js';
import StencilConstants from './StencilConstants.js';
import SunLight from './SunLight.js';
import SunPostProcess from './SunPostProcess.js';
import TweenCollection from './TweenCollection.js';
import View from './View.js';

    var requestRenderAfterFrame = function (scene) {
        return function () {
            scene.frameState.afterRender.push(function() {
                scene.requestRender();
            });
        };
    };

    /**
     * The container for all 3D graphical objects and state in a Cesium virtual scene.  Generally,
     * a scene is not created directly; instead, it is implicitly created by {@link CesiumWidget}.
     * <p>
     * <em><code>contextOptions</code> parameter details:</em>
     * </p>
     * <p>
     * The default values are:
     * <code>
     * {
     *   webgl : {
     *     alpha : false,
     *     depth : true,
     *     stencil : false,
     *     antialias : true,
     *     powerPreference: 'high-performance',
     *     premultipliedAlpha : true,
     *     preserveDrawingBuffer : false,
     *     failIfMajorPerformanceCaveat : false
     *   },
     *   allowTextureFilterAnisotropic : true
     * }
     * </code>
     * </p>
     * <p>
     * The <code>webgl</code> property corresponds to the {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}
     * object used to create the WebGL context.
     * </p>
     * <p>
     * <code>webgl.alpha</code> defaults to false, which can improve performance compared to the standard WebGL default
     * of true.  If an application needs to composite Cesium above other HTML elements using alpha-blending, set
     * <code>webgl.alpha</code> to true.
     * </p>
     * <p>
     * The other <code>webgl</code> properties match the WebGL defaults for {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}.
     * </p>
     * <p>
     * <code>allowTextureFilterAnisotropic</code> defaults to true, which enables anisotropic texture filtering when the
     * WebGL extension is supported.  Setting this to false will improve performance, but hurt visual quality, especially for horizon views.
     * </p>
     *
     * @alias Scene
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Canvas} options.canvas The HTML canvas element to create the scene for.
     * @param {Object} [options.contextOptions] Context and WebGL creation properties.  See details above.
     * @param {Element} [options.creditContainer] The HTML element in which the credits will be displayed.
     * @param {Element} [options.creditViewport] The HTML element in which to display the credit popup.  If not specified, the viewport will be a added as a sibling of the canvas.
     * @param {MapProjection} [options.mapProjection=new GeographicProjection()] The map projection to use in 2D and Columbus View modes.
     * @param {Boolean} [options.orderIndependentTranslucency=true] If true and the configuration supports it, use order independent translucency.
     * @param {Boolean} [options.scene3DOnly=false] If true, optimizes memory use and performance for 3D mode but disables the ability to use 2D or Columbus View.
     * @param {Number} [options.terrainExaggeration=1.0] A scalar used to exaggerate the terrain. Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
     * @param {Boolean} [options.shadows=false] Determines if shadows are cast by light sources.
     * @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
     * @param {Boolean} [options.requestRenderMode=false] If true, rendering a frame will only occur when needed as determined by changes within the scene. Enabling improves performance of the application, but requires using {@link Scene#requestRender} to render a new frame explicitly in this mode. This will be necessary in many cases after making changes to the scene in other parts of the API. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}.
     * @param {Number} [options.maximumRenderTimeChange=0.0] If requestRenderMode is true, this value defines the maximum change in simulation time allowed before a render is requested. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}.
     *
     * @see CesiumWidget
     * @see {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}
     *
     * @exception {DeveloperError} options and options.canvas are required.
     *
     * @example
     * // Create scene without anisotropic texture filtering
     * var scene = new Cesium.Scene({
     *   canvas : canvas,
     *   contextOptions : {
     *     allowTextureFilterAnisotropic : false
     *   }
     * });
     */
    function Scene(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var canvas = options.canvas;
        var creditContainer = options.creditContainer;
        var creditViewport = options.creditViewport;

        var contextOptions = clone(options.contextOptions);
        if (!defined(contextOptions)) {
            contextOptions = {};
        }
        if (!defined(contextOptions.webgl)) {
            contextOptions.webgl = {};
        }
        contextOptions.webgl.powerPreference = defaultValue(contextOptions.webgl.powerPreference, 'high-performance');

        //>>includeStart('debug', pragmas.debug);
        if (!defined(canvas)) {
            throw new DeveloperError('options and options.canvas are required.');
        }
        //>>includeEnd('debug');
        var hasCreditContainer = defined(creditContainer);
        var context = new Context(canvas, contextOptions);
        if (!hasCreditContainer) {
            creditContainer = document.createElement('div');
            creditContainer.style.position = 'absolute';
            creditContainer.style.bottom = '0';
            creditContainer.style['text-shadow'] = '0 0 2px #000000';
            creditContainer.style.color = '#ffffff';
            creditContainer.style['font-size'] = '10px';
            creditContainer.style['padding-right'] = '5px';
            canvas.parentNode.appendChild(creditContainer);
        }
        if (!defined(creditViewport)) {
            creditViewport = canvas.parentNode;
        }

        this._id = createGuid();
        this._jobScheduler = new JobScheduler();
        this._frameState = new FrameState(context, new CreditDisplay(creditContainer, ' • ', creditViewport), this._jobScheduler);
        this._frameState.scene3DOnly = defaultValue(options.scene3DOnly, false);
        this._removeCreditContainer = !hasCreditContainer;
        this._creditContainer = creditContainer;

        this._canvas = canvas;
        this._context = context;
        this._computeEngine = new ComputeEngine(context);
        this._globe = undefined;
        this._primitives = new PrimitiveCollection();
        this._groundPrimitives = new PrimitiveCollection();

        this._logDepthBuffer = context.fragmentDepth;
        this._logDepthBufferDirty = true;

        this._tweens = new TweenCollection();

        this._shaderFrameCount = 0;

        this._sunPostProcess = undefined;

        this._computeCommandList = [];
        this._overlayCommandList = [];

        this._useOIT = defaultValue(options.orderIndependentTranslucency, true);
        this._executeOITFunction = undefined;

        this._depthPlane = new DepthPlane();

        this._clearColorCommand = new ClearCommand({
            color : new Color(),
            stencil : 0,
            owner : this
        });
        this._depthClearCommand = new ClearCommand({
            depth : 1.0,
            owner : this
        });
        this._stencilClearCommand = new ClearCommand({
            stencil : 0
        });
        this._classificationStencilClearCommand = new ClearCommand({
            stencil : 0,
            renderState : RenderState.fromCache({
                stencilMask : StencilConstants.CLASSIFICATION_MASK
            })
        });

        this._depthOnlyRenderStateCache = {};

        this._transitioner = new SceneTransitioner(this);

        this._preUpdate = new Event();
        this._postUpdate = new Event();

        this._renderError = new Event();
        this._preRender = new Event();
        this._postRender = new Event();

        this._minimumDisableDepthTestDistance = 0.0;

        /**
         * Exceptions occurring in <code>render</code> are always caught in order to raise the
         * <code>renderError</code> event.  If this property is true, the error is rethrown
         * after the event is raised.  If this property is false, the <code>render</code> function
         * returns normally after raising the event.
         *
         * @type {Boolean}
         * @default false
         */
        this.rethrowRenderErrors = false;

        /**
         * Determines whether or not to instantly complete the
         * scene transition animation on user input.
         *
         * @type {Boolean}
         * @default true
         */
        this.completeMorphOnUserInput = true;

        /**
         * The event fired at the beginning of a scene transition.
         * @type {Event}
         * @default Event()
         */
        this.morphStart = new Event();

        /**
         * The event fired at the completion of a scene transition.
         * @type {Event}
         * @default Event()
         */
        this.morphComplete = new Event();

        /**
         * The {@link SkyBox} used to draw the stars.
         *
         * @type {SkyBox}
         * @default undefined
         *
         * @see Scene#backgroundColor
         */
        this.skyBox = undefined;

        /**
         * The sky atmosphere drawn around the globe.
         *
         * @type {SkyAtmosphere}
         * @default undefined
         */
        this.skyAtmosphere = undefined;

        /**
         * The {@link Sun}.
         *
         * @type {Sun}
         * @default undefined
         */
        this.sun = undefined;

        /**
         * Uses a bloom filter on the sun when enabled.
         *
         * @type {Boolean}
         * @default true
         */
        this.sunBloom = true;
        this._sunBloom = undefined;

        /**
         * The {@link Moon}
         *
         * @type Moon
         * @default undefined
         */
        this.moon = undefined;

        /**
         * The background color, which is only visible if there is no sky box, i.e., {@link Scene#skyBox} is undefined.
         *
         * @type {Color}
         * @default {@link Color.BLACK}
         *
         * @see Scene#skyBox
         */
        this.backgroundColor = Color.clone(Color.BLACK);

        this._mode = SceneMode.SCENE3D;

        this._mapProjection = defined(options.mapProjection) ? options.mapProjection : new GeographicProjection();

        /**
         * The current morph transition time between 2D/Columbus View and 3D,
         * with 0.0 being 2D or Columbus View and 1.0 being 3D.
         *
         * @type {Number}
         * @default 1.0
         */
        this.morphTime = 1.0;

        /**
         * The far-to-near ratio of the multi-frustum when using a normal depth buffer.
         * <p>
         * This value is used to create the near and far values for each frustum of the multi-frustum. It is only used
         * when {@link Scene#logarithmicDepthBuffer} is <code>false</code>. When <code>logarithmicDepthBuffer</code> is
         * <code>true</code>, use {@link Scene#logarithmicDepthFarToNearRatio}.
         * </p>
         *
         * @type {Number}
         * @default 1000.0
         */
        this.farToNearRatio = 1000.0;

        /**
         * The far-to-near ratio of the multi-frustum when using a logarithmic depth buffer.
         * <p>
         * This value is used to create the near and far values for each frustum of the multi-frustum. It is only used
         * when {@link Scene#logarithmicDepthBuffer} is <code>true</code>. When <code>logarithmicDepthBuffer</code> is
         * <code>false</code>, use {@link Scene#farToNearRatio}.
         * </p>
         *
         * @type {Number}
         * @default 1e9
         */
        this.logarithmicDepthFarToNearRatio = 1e9;

        /**
         * Determines the uniform depth size in meters of each frustum of the multifrustum in 2D. If a primitive or model close
         * to the surface shows z-fighting, decreasing this will eliminate the artifact, but decrease performance. On the
         * other hand, increasing this will increase performance but may cause z-fighting among primitives close to the surface.
         *
         * @type {Number}
         * @default 1.75e6
         */
        this.nearToFarDistance2D = 1.75e6;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * A function that determines what commands are executed.  As shown in the examples below,
         * the function receives the command's <code>owner</code> as an argument, and returns a boolean indicating if the
         * command should be executed.
         * </p>
         * <p>
         * The default is <code>undefined</code>, indicating that all commands are executed.
         * </p>
         *
         * @type Function
         *
         * @default undefined
         *
         * @example
         * // Do not execute any commands.
         * scene.debugCommandFilter = function(command) {
         *     return false;
         * };
         *
         * // Execute only the billboard's commands.  That is, only draw the billboard.
         * var billboards = new Cesium.BillboardCollection();
         * scene.debugCommandFilter = function(command) {
         *     return command.owner === billboards;
         * };
         */
        this.debugCommandFilter = undefined;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * When <code>true</code>, commands are randomly shaded.  This is useful
         * for performance analysis to see what parts of a scene or model are
         * command-dense and could benefit from batching.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.debugShowCommands = false;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * When <code>true</code>, commands are shaded based on the frustums they
         * overlap.  Commands in the closest frustum are tinted red, commands in
         * the next closest are green, and commands in the farthest frustum are
         * blue.  If a command overlaps more than one frustum, the color components
         * are combined, e.g., a command overlapping the first two frustums is tinted
         * yellow.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.debugShowFrustums = false;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * Displays frames per second and time between frames.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.debugShowFramesPerSecond = false;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * Displays depth information for the indicated frustum.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.debugShowGlobeDepth = false;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * Indicates which frustum will have depth information displayed.
         * </p>
         *
         * @type Number
         *
         * @default 1
         */
        this.debugShowDepthFrustum = 1;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * When <code>true</code>, draws outlines to show the boundaries of the camera frustums
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.debugShowFrustumPlanes = false;
        this._debugShowFrustumPlanes = false;
        this._debugFrustumPlanes = undefined;

        /**
         * When <code>true</code>, enables picking using the depth buffer.
         *
         * @type Boolean
         * @default true
         */
        this.useDepthPicking = true;

        /**
         * When <code>true</code>, enables picking translucent geometry using the depth buffer. Note that {@link Scene#useDepthPicking} must also be true for enabling this to work.
         *
         * <p>
         * Render must be called between picks.
         * <br>There is a decrease in performance when enabled. There are extra draw calls to write depth for
         * translucent geometry.
         * </p>
         *
         * @example
         * // picking the position of a translucent primitive
         * viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) {
         *      var pickedFeature = viewer.scene.pick(movement.position);
         *      if (!Cesium.defined(pickedFeature)) {
         *          // nothing picked
         *          return;
         *      }
         *      viewer.scene.render();
         *      var worldPosition = viewer.scene.pickPosition(movement.position);
         * }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
         *
         * @type {Boolean}
         * @default false
         */
        this.pickTranslucentDepth = false;

        /**
         * The time in milliseconds to wait before checking if the camera has not moved and fire the cameraMoveEnd event.
         * @type {Number}
         * @default 500.0
         * @private
         */
        this.cameraEventWaitTime = 500.0;

        /**
         * Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional
         * performance improvements by rendering less geometry and dispatching less terrain requests.
         * @type {Fog}
         */
        this.fog = new Fog();

        this._shadowMapCamera = new Camera(this);

        /**
         * The shadow map for the scene's light source. When enabled, models, primitives, and the globe may cast and receive shadows.
         * @type {ShadowMap}
         */
        this.shadowMap = new ShadowMap({
            context : context,
            lightCamera : this._shadowMapCamera,
            enabled : defaultValue(options.shadows, false)
        });

        /**
         * When <code>false</code>, 3D Tiles will render normally. When <code>true</code>, classified 3D Tile geometry will render normally and
         * unclassified 3D Tile geometry will render with the color multiplied by {@link Scene#invertClassificationColor}.
         * @type {Boolean}
         * @default false
         */
        this.invertClassification = false;

        /**
         * The highlight color of unclassified 3D Tile geometry when {@link Scene#invertClassification} is <code>true</code>.
         * <p>When the color's alpha is less than 1.0, the unclassified portions of the 3D Tiles will not blend correctly with the classified positions of the 3D Tiles.</p>
         * <p>Also, when the color's alpha is less than 1.0, the WEBGL_depth_texture and EXT_frag_depth WebGL extensions must be supported.</p>
         * @type {Color}
         * @default Color.WHITE
         */
        this.invertClassificationColor = Color.clone(Color.WHITE);

        this._actualInvertClassificationColor = Color.clone(this._invertClassificationColor);
        this._invertClassification = new InvertClassification();

        /**
         * The focal length for use when with cardboard or WebVR.
         * @type {Number}
         */
        this.focalLength = undefined;

        /**
         * The eye separation distance in meters for use with cardboard or WebVR.
         * @type {Number}
         */
        this.eyeSeparation = undefined;

        /**
         * Post processing effects applied to the final render.
         * @type {PostProcessStageCollection}
         */
        this.postProcessStages = new PostProcessStageCollection();

        this._brdfLutGenerator = new BrdfLutGenerator();

        this._terrainExaggeration = defaultValue(options.terrainExaggeration, 1.0);

        this._performanceDisplay = undefined;
        this._debugVolume = undefined;

        this._screenSpaceCameraController = new ScreenSpaceCameraController(this);
        this._mapMode2D = defaultValue(options.mapMode2D, MapMode2D.INFINITE_SCROLL);

        // Keeps track of the state of a frame. FrameState is the state across
        // the primitives of the scene. This state is for internally keeping track
        // of celestial and environment effects that need to be updated/rendered in
        // a certain order as well as updating/tracking framebuffer usage.
        this._environmentState = {
            skyBoxCommand : undefined,
            skyAtmosphereCommand : undefined,
            sunDrawCommand : undefined,
            sunComputeCommand : undefined,
            moonCommand : undefined,

            isSunVisible : false,
            isMoonVisible : false,
            isReadyForAtmosphere : false,
            isSkyAtmosphereVisible : false,

            clearGlobeDepth : false,
            useDepthPlane : false,
            renderTranslucentDepthForPick : false,

            originalFramebuffer : undefined,
            useGlobeDepthFramebuffer : false,
            separatePrimitiveFramebuffer : false,
            useOIT : false,
            useInvertClassification : false,
            usePostProcess : false,
            usePostProcessSelected : false,
            useWebVR : false
        };

        this._useWebVR = false;
        this._cameraVR = undefined;
        this._aspectRatioVR = undefined;

        /**
         * When <code>true</code>, rendering a frame will only occur when needed as determined by changes within the scene.
         * Enabling improves performance of the application, but requires using {@link Scene#requestRender}
         * to render a new frame explicitly in this mode. This will be necessary in many cases after making changes
         * to the scene in other parts of the API.
         *
         * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
         * @see Scene#maximumRenderTimeChange
         * @see Scene#requestRender
         *
         * @type {Boolean}
         * @default false
         */
        this.requestRenderMode = defaultValue(options.requestRenderMode, false);
        this._renderRequested = true;

        /**
         * If {@link Scene#requestRenderMode} is <code>true</code>, this value defines the maximum change in
         * simulation time allowed before a render is requested. Lower values increase the number of frames rendered
         * and higher values decrease the number of frames rendered. If <code>undefined</code>, changes to
         * the simulation time will never request a render.
         * This value impacts the rate of rendering for changes in the scene like lighting, entity property updates,
         * and animations.
         *
         * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
         * @see Scene#requestRenderMode
         *
         * @type {Number}
         * @default 0.5
         */
        this.maximumRenderTimeChange = defaultValue(options.maximumRenderTimeChange, 0.0);
        this._lastRenderTime = undefined;
        this._frameRateMonitor = undefined;

        this._removeRequestListenerCallback = RequestScheduler.requestCompletedEvent.addEventListener(requestRenderAfterFrame(this));
        this._removeTaskProcessorListenerCallback = TaskProcessor.taskCompletedEvent.addEventListener(requestRenderAfterFrame(this));
        this._removeGlobeCallbacks = [];

        var viewport = new BoundingRectangle(0, 0, context.drawingBufferWidth, context.drawingBufferHeight);
        var camera = new Camera(this);

        if (this._logDepthBuffer) {
            camera.frustum.near = 0.1;
            camera.frustum.far = 10000000000.0;
        }

        /**
         * The camera view for the scene camera flight destination. Used for preloading flight destination tiles.
         * @type {Camera}
         * @private
         */
        this.preloadFlightCamera = new Camera(this);

        /**
         * The culling volume for the scene camera flight destination. Used for preloading flight destination tiles.
         * @type {CullingVolume}
         * @private
         */
        this.preloadFlightCullingVolume = undefined;

        this._picking = new Picking(this);
        this._defaultView = new View(this, camera, viewport);
        this._view = this._defaultView;

        this._hdr = undefined;
        this._hdrDirty = undefined;
        this.highDynamicRange = false;
        this.gamma = 2.2;

        /**
         * The spherical harmonic coefficients for image-based lighting of PBR models.
         * @type {Cartesian3[]}
         */
        this.sphericalHarmonicCoefficients = undefined;

        /**
         * The url to the KTX file containing the specular environment map and convoluted mipmaps for image-based lighting of PBR models.
         * @type {String}
         */
        this.specularEnvironmentMaps = undefined;
        this._specularEnvironmentMapAtlas = undefined;

        /**
         * The light source for shading. Defaults to a directional light from the Sun.
         * @type {Light}
         */
        this.light = new SunLight();

        // Give frameState, camera, and screen space camera controller initial state before rendering
        updateFrameNumber(this, 0.0, JulianDate.now());
        this.updateFrameState();
        this.initializeFrame();
    }

    function updateGlobeListeners(scene, globe) {
        for (var i = 0; i < scene._removeGlobeCallbacks.length; ++i) {
            scene._removeGlobeCallbacks[i]();
        }
        scene._removeGlobeCallbacks.length = 0;

        var removeGlobeCallbacks = [];
        if (defined(globe)) {
            removeGlobeCallbacks.push(globe.imageryLayersUpdatedEvent.addEventListener(requestRenderAfterFrame(scene)));
            removeGlobeCallbacks.push(globe.terrainProviderChanged.addEventListener(requestRenderAfterFrame(scene)));
        }
        scene._removeGlobeCallbacks = removeGlobeCallbacks;
    }

    var scratchSunColor = new Cartesian4();

    Object.defineProperties(Scene.prototype, {
        /**
         * Gets the canvas element to which this scene is bound.
         * @memberof Scene.prototype
         *
         * @type {Canvas}
         * @readonly
         */
        canvas : {
            get : function() {
                return this._canvas;
            }
        },

        /**
         * The drawingBufferHeight of the underlying GL context.
         * @memberof Scene.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight}
         */
        drawingBufferHeight : {
            get : function() {
                return this._context.drawingBufferHeight;
            }
        },

        /**
         * The drawingBufferHeight of the underlying GL context.
         * @memberof Scene.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight}
         */
        drawingBufferWidth : {
            get : function() {
                return this._context.drawingBufferWidth;
            }
        },

        /**
         * The maximum aliased line width, in pixels, supported by this WebGL implementation.  It will be at least one.
         * @memberof Scene.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>ALIASED_LINE_WIDTH_RANGE</code>.
         */
        maximumAliasedLineWidth : {
            get : function() {
                return ContextLimits.maximumAliasedLineWidth;
            }
        },

        /**
         * The maximum length in pixels of one edge of a cube map, supported by this WebGL implementation.  It will be at least 16.
         * @memberof Scene.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>GL_MAX_CUBE_MAP_TEXTURE_SIZE</code>.
         */
        maximumCubeMapSize : {
            get : function() {
                return ContextLimits.maximumCubeMapSize;
            }
        },

        /**
         * Returns <code>true</code> if the {@link Scene#pickPosition} function is supported.
         * @memberof Scene.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @see Scene#pickPosition
         */
        pickPositionSupported : {
            get : function() {
                return this._context.depthTexture;
            }
        },

        /**
         * Returns <code>true</code> if the {@link Scene#sampleHeight} and {@link Scene#sampleHeightMostDetailed} functions are supported.
         * @memberof Scene.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @see Scene#sampleHeight
         * @see Scene#sampleHeightMostDetailed
         */
        sampleHeightSupported : {
            get : function() {
                return this._context.depthTexture;
            }
        },

        /**
         * Returns <code>true</code> if the {@link Scene#clampToHeight} and {@link Scene#clampToHeightMostDetailed} functions are supported.
         * @memberof Scene.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @see Scene#clampToHeight
         * @see Scene#clampToHeightMostDetailed
         */
        clampToHeightSupported : {
            get : function() {
                return this._context.depthTexture;
            }
        },

        /**
         * Returns <code>true</code> if the {@link Scene#invertClassification} is supported.
         * @memberof Scene.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @see Scene#invertClassification
         */
        invertClassificationSupported : {
            get : function() {
                return this._context.depthTexture;
            }
        },

        /**
         * Returns <code>true</code> if specular environment maps are supported.
         * @memberof Scene.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @see Scene#specularEnvironmentMaps
         */
        specularEnvironmentMapsSupported : {
            get : function() {
                return OctahedralProjectedCubeMap.isSupported(this._context);
            }
        },

        /**
         * Gets or sets the depth-test ellipsoid.
         * @memberof Scene.prototype
         *
         * @type {Globe}
         */
        globe : {
            get: function() {
                return this._globe;
            },

            set: function(globe) {
                this._globe = this._globe && this._globe.destroy();
                this._globe = globe;

                updateGlobeListeners(this, globe);
            }
        },

        /**
         * Gets the collection of primitives.
         * @memberof Scene.prototype
         *
         * @type {PrimitiveCollection}
         * @readonly
         */
        primitives : {
            get : function() {
                return this._primitives;
            }
        },

        /**
         * Gets the collection of ground primitives.
         * @memberof Scene.prototype
         *
         * @type {PrimitiveCollection}
         * @readonly
         */
        groundPrimitives : {
            get : function() {
                return this._groundPrimitives;
            }
        },

        /**
         * Gets or sets the camera.
         * @memberof Scene.prototype
         *
         * @type {Camera}
         * @readonly
         */
        camera : {
            get : function() {
                return this._view.camera;
            },
            set : function(camera) {
                // For internal use only. Documentation is still @readonly.
                this._view.camera = camera;
            }
        },

        /**
         * Gets or sets the view.
         * @memberof Scene.prototype
         *
         * @type {View}
         * @readonly
         *
         * @private
         */
        view : {
            get : function() {
                return this._view;
            },
            set : function(view) {
                // For internal use only. Documentation is still @readonly.
                this._view = view;
            }
        },

        /**
         * Gets the default view.
         * @memberof Scene.prototype
         *
         * @type {View}
         * @readonly
         *
         * @private
         */
        defaultView : {
            get : function() {
                return this._defaultView;
            }
        },

        /**
         * Gets picking functions and state
         * @memberof Scene.prototype
         *
         * @type {Picking}
         * @readonly
         *
         * @private
         */
        picking : {
            get : function() {
                return this._picking;
            }
        },

        /**
         * Gets the controller for camera input handling.
         * @memberof Scene.prototype
         *
         * @type {ScreenSpaceCameraController}
         * @readonly
         */
        screenSpaceCameraController : {
            get : function() {
                return this._screenSpaceCameraController;
            }
        },

        /**
         * Get the map projection to use in 2D and Columbus View modes.
         * @memberof Scene.prototype
         *
         * @type {MapProjection}
         * @readonly
         *
         * @default new GeographicProjection()
         */
        mapProjection : {
            get: function() {
                return this._mapProjection;
            }
        },

        /**
         * Gets the job scheduler
         * @memberof Scene.prototype
         * @type {JobScheduler}
         * @readonly
         *
         * @private
         */
        jobScheduler : {
            get: function() {
                return this._jobScheduler;
            }
        },

        /**
         * Gets state information about the current scene. If called outside of a primitive's <code>update</code>
         * function, the previous frame's state is returned.
         * @memberof Scene.prototype
         *
         * @type {FrameState}
         * @readonly
         *
         * @private
         */
        frameState : {
            get: function() {
                return this._frameState;
            }
        },

        /**
         * Gets the environment state.
         * @memberof Scene.prototype
         *
         * @type {EnvironmentState}
         * @readonly
         *
         * @private
         */
        environmentState : {
            get: function() {
                return this._environmentState;
            }
        },

        /**
         * Gets the collection of tweens taking place in the scene.
         * @memberof Scene.prototype
         *
         * @type {TweenCollection}
         * @readonly
         *
         * @private
         */
        tweens : {
            get : function() {
                return this._tweens;
            }
        },

        /**
         * Gets the collection of image layers that will be rendered on the globe.
         * @memberof Scene.prototype
         *
         * @type {ImageryLayerCollection}
         * @readonly
         */
        imageryLayers : {
            get : function() {
                if (!defined(this.globe)) {
                    return undefined;
                }

                return this.globe.imageryLayers;
            }
        },

        /**
         * The terrain provider providing surface geometry for the globe.
         * @memberof Scene.prototype
         *
         * @type {TerrainProvider}
         */
        terrainProvider : {
            get : function() {
                if (!defined(this.globe)) {
                    return undefined;
                }

                return this.globe.terrainProvider;
            },
            set : function(terrainProvider) {
                if (defined(this.globe)) {
                    this.globe.terrainProvider = terrainProvider;
                }
            }
        },

        /**
         * Gets an event that's raised when the terrain provider is changed
         * @memberof Scene.prototype
         *
         * @type {Event}
         * @readonly
         */
        terrainProviderChanged : {
            get : function() {
                if (!defined(this.globe)) {
                    return undefined;
                }

                return this.globe.terrainProviderChanged;
            }
        },

        /**
         * Gets the event that will be raised before the scene is updated or rendered.  Subscribers to the event
         * receive the Scene instance as the first parameter and the current time as the second parameter.
         * @memberof Scene.prototype
         *
         * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
         * @see Scene#postUpdate
         * @see Scene#preRender
         * @see Scene#postRender
         *
         * @type {Event}
         * @readonly
         */
        preUpdate : {
            get : function() {
                return this._preUpdate;
            }
        },

        /**
         * Gets the event that will be raised immediately after the scene is updated and before the scene is rendered.
         * Subscribers to the event receive the Scene instance as the first parameter and the current time as the second
         * parameter.
         * @memberof Scene.prototype
         *
         * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
         * @see Scene#preUpdate
         * @see Scene#preRender
         * @see Scene#postRender
         *
         * @type {Event}
         * @readonly
         */
        postUpdate : {
            get : function() {
                return this._postUpdate;
            }
        },

        /**
         * Gets the event that will be raised when an error is thrown inside the <code>render</code> function.
         * The Scene instance and the thrown error are the only two parameters passed to the event handler.
         * By default, errors are not rethrown after this event is raised, but that can be changed by setting
         * the <code>rethrowRenderErrors</code> property.
         * @memberof Scene.prototype
         *
         * @type {Event}
         * @readonly
         */
        renderError : {
            get : function() {
                return this._renderError;
            }
        },

        /**
         * Gets the event that will be raised after the scene is updated and immediately before the scene is rendered.
         * Subscribers to the event receive the Scene instance as the first parameter and the current time as the second
         * parameter.
         * @memberof Scene.prototype
         *
         * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
         * @see Scene#preUpdate
         * @see Scene#postUpdate
         * @see Scene#postRender
         *
         * @type {Event}
         * @readonly
         */
        preRender : {
            get : function() {
                return this._preRender;
            }
        },

        /**
         * Gets the event that will be raised immediately after the scene is rendered.  Subscribers to the event
         * receive the Scene instance as the first parameter and the current time as the second parameter.
         * @memberof Scene.prototype
         *
         * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}
         * @see Scene#preUpdate
         * @see Scene#postUpdate
         * @see Scene#postRender
         *
         * @type {Event}
         * @readonly
         */
        postRender : {
            get : function() {
                return this._postRender;
            }
        },

        /**
         * Gets the simulation time when the scene was last rendered. Returns undefined if the scene has not yet been
         * rendered.
         * @memberof Scene.prototype
         *
         * @type {JulianDate}
         * @readonly
         */
        lastRenderTime : {
            get : function() {
                return this._lastRenderTime;
            }
        },

        /**
         * @memberof Scene.prototype
         * @private
         * @readonly
         */
        context : {
            get : function() {
                return this._context;
            }
        },

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * When {@link Scene.debugShowFrustums} is <code>true</code>, this contains
         * properties with statistics about the number of command execute per frustum.
         * <code>totalCommands</code> is the total number of commands executed, ignoring
         * overlap. <code>commandsInFrustums</code> is an array with the number of times
         * commands are executed redundantly, e.g., how many commands overlap two or
         * three frustums.
         * </p>
         *
         * @memberof Scene.prototype
         *
         * @type {Object}
         * @readonly
         *
         * @default undefined
         */
        debugFrustumStatistics : {
            get : function() {
                return this._view.debugFrustumStatistics;
            }
        },

        /**
         * Gets whether or not the scene is optimized for 3D only viewing.
         * @memberof Scene.prototype
         * @type {Boolean}
         * @readonly
         */
        scene3DOnly : {
            get : function() {
                return this._frameState.scene3DOnly;
            }
        },

        /**
         * Gets whether or not the scene has order independent translucency enabled.
         * Note that this only reflects the original construction option, and there are
         * other factors that could prevent OIT from functioning on a given system configuration.
         * @memberof Scene.prototype
         * @type {Boolean}
         * @readonly
         */
        orderIndependentTranslucency : {
            get : function() {
                return this._useOIT;
            }
        },

        /**
         * Gets the unique identifier for this scene.
         * @memberof Scene.prototype
         * @type {String}
         * @readonly
         */
        id : {
            get : function() {
                return this._id;
            }
        },

        /**
         * Gets or sets the current mode of the scene.
         * @memberof Scene.prototype
         * @type {SceneMode}
         * @default {@link SceneMode.SCENE3D}
         */
        mode : {
            get : function() {
                return this._mode;
            },
            set : function(value) {
                //>>includeStart('debug', pragmas.debug);
                if (this.scene3DOnly && value !== SceneMode.SCENE3D) {
                    throw new DeveloperError('Only SceneMode.SCENE3D is valid when scene3DOnly is true.');
                }
                //>>includeEnd('debug');
                if (value === SceneMode.SCENE2D) {
                    this.morphTo2D(0);
                } else if (value === SceneMode.SCENE3D) {
                    this.morphTo3D(0);
                } else if (value === SceneMode.COLUMBUS_VIEW) {
                    this.morphToColumbusView(0);
                    //>>includeStart('debug', pragmas.debug);
                } else {
                    throw new DeveloperError('value must be a valid SceneMode enumeration.');
                    //>>includeEnd('debug');
                }
                this._mode = value;
            }
        },

        /**
         * Gets the number of frustums used in the last frame.
         * @memberof Scene.prototype
         * @type {FrustumCommands[]}
         *
         * @private
         */
        frustumCommandsList : {
            get : function() {
                return this._view.frustumCommandsList;
            }
        },

        /**
         * Gets the number of frustums used in the last frame.
         * @memberof Scene.prototype
         * @type {Number}
         *
         * @private
         */
        numberOfFrustums : {
            get : function() {
                return this._view.frustumCommandsList.length;
            }
        },

        /**
         * Gets the scalar used to exaggerate the terrain.
         * @memberof Scene.prototype
         * @type {Number}
         */
        terrainExaggeration : {
            get : function() {
                return this._terrainExaggeration;
            }
        },

        /**
         * When <code>true</code>, splits the scene into two viewports with steroscopic views for the left and right eyes.
         * Used for cardboard and WebVR.
         * @memberof Scene.prototype
         * @type {Boolean}
         * @default false
         */
        useWebVR : {
            get : function() {
                return this._useWebVR;
            },
            set : function(value) {
                //>>includeStart('debug', pragmas.debug);
                if (this.camera.frustum instanceof OrthographicFrustum) {
                    throw new DeveloperError('VR is unsupported with an orthographic projection.');
                }
                //>>includeEnd('debug');
                this._useWebVR = value;
                if (this._useWebVR) {
                    this._frameState.creditDisplay.container.style.visibility = 'hidden';
                    this._cameraVR = new Camera(this);
                    if (!defined(this._deviceOrientationCameraController)) {
                        this._deviceOrientationCameraController = new DeviceOrientationCameraController(this);
                    }

                    this._aspectRatioVR = this.camera.frustum.aspectRatio;
                } else {
                    this._frameState.creditDisplay.container.style.visibility = 'visible';
                    this._cameraVR = undefined;
                    this._deviceOrientationCameraController = this._deviceOrientationCameraController && !this._deviceOrientationCameraController.isDestroyed() && this._deviceOrientationCameraController.destroy();

                    this.camera.frustum.aspectRatio = this._aspectRatioVR;
                    this.camera.frustum.xOffset = 0.0;
                }
            }
        },

        /**
         * Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
         * @memberof Scene.prototype
         * @type {MapMode2D}
         */
        mapMode2D : {
            get : function() {
                return this._mapMode2D;
            }
        },

        /**
         * Gets or sets the position of the Imagery splitter within the viewport.  Valid values are between 0.0 and 1.0.
         * @memberof Scene.prototype
         *
         * @type {Number}
         */
        imagerySplitPosition : {
            get: function() {
                return this._frameState.imagerySplitPosition;
            },
            set: function(value) {
                this._frameState.imagerySplitPosition = value;
            }
        },

        /**
         * The distance from the camera at which to disable the depth test of billboards, labels and points
         * to, for example, prevent clipping against terrain. When set to zero, the depth test should always
         * be applied. When less than zero, the depth test should never be applied. Setting the disableDepthTestDistance
         * property of a billboard, label or point will override this value.
         * @memberof Scene.prototype
         * @type {Number}
         * @default 0.0
         */
        minimumDisableDepthTestDistance : {
            get : function() {
                return this._minimumDisableDepthTestDistance;
            },
            set : function(value) {
                //>>includeStart('debug', pragmas.debug);
                if (!defined(value) || value < 0.0) {
                    throw new DeveloperError('minimumDisableDepthTestDistance must be greater than or equal to 0.0.');
                }
                //>>includeEnd('debug');
                this._minimumDisableDepthTestDistance = value;
            }
        },

        /**
         * Whether or not to use a logarithmic depth buffer. Enabling this option will allow for less frustums in the multi-frustum,
         * increasing performance. This property relies on fragmentDepth being supported.
         * @memberof Scene.prototype
         * @type {Boolean}
         */
        logarithmicDepthBuffer : {
            get : function() {
                return this._logDepthBuffer;
            },
            set : function(value) {
                value = this._context.fragmentDepth && value;
                if (this._logDepthBuffer !== value) {
                    this._logDepthBuffer = value;
                    this._logDepthBufferDirty = true;
                    this._defaultView.updateFrustums = true;
                }
            }
        },

        /**
         * The value used for gamma correction. This is only used when rendering with high dynamic range.
         * @memberof Scene.prototype
         * @type {Number}
         * @default 2.2
         */
        gamma : {
            get : function() {
                return this._context.uniformState.gamma;
            },
            set : function(value) {
                this._context.uniformState.gamma = value;
            }
        },

        /**
         * Whether or not to use high dynamic range rendering.
         * @memberof Scene.prototype
         * @type {Boolean}
         * @default true
         */
        highDynamicRange : {
            get : function() {
                return this._hdr;
            },
            set : function(value) {
                var context = this._context;
                var hdr = value && context.depthTexture && (context.colorBufferFloat || context.colorBufferHalfFloat);
                this._hdrDirty = hdr !== this._hdr;
                this._hdr = hdr;
            }
        },

        /**
         * Whether or not high dynamic range rendering is supported.
         * @memberof Scene.prototype
         * @type {Boolean}
         * @default true
         */
        highDynamicRangeSupported : {
            get : function() {
                var context = this._context;
                return context.depthTexture && (context.colorBufferFloat || context.colorBufferHalfFloat);
            }
        },

        /**
         * Gets or sets the color of the light emitted by the sun.
         *
         * @memberof Scene.prototype
         * @type {Cartesian3}
         * @default Cartesian3(1.8, 1.85, 2.0)
         */
        sunColor: {
            get: function() {
                deprecationWarning('sun-color-removed', 'scene.sunColor will be removed in Cesium 1.69. Use scene.light.color and scene.light.intensity instead.');
                return this.light.color;
            },
            set: function(value) {
                deprecationWarning('sun-color-removed', 'scene.sunColor will be removed in Cesium 1.69. Use scene.light.color and scene.light.intensity instead.');
                var maximumComponent = Cartesian3.maximumComponent(value);
                var sunColor = Cartesian4.fromElements(value.x, value.y, value.z, 1.0, scratchSunColor);
                var intensity = 1.0;
                if (maximumComponent > 1.0) {
                    Cartesian3.divideByScalar(sunColor, maximumComponent, sunColor); // Don't divide alpha channel
                    intensity = maximumComponent;
                }
                this.light.color = Color.fromCartesian4(sunColor, this.light.color);
                this.light.intensity = intensity;
            }
        },

        /**
         * Ratio between a pixel and a density-independent pixel. Provides a standard unit of
         * measure for real pixel measurements appropriate to a particular device.
         *
         * @memberof Scene.prototype
         * @type {Number}
         * @default 1.0
         * @private
         */
        pixelRatio: {
            get: function() {
                return this._frameState.pixelRatio;
            },
            set: function(value) {
                this._frameState.pixelRatio = value;
            }
        },

        /**
         * @private
         */
        opaqueFrustumNearOffset : {
            get : function() {
                return this._frameState.useLogDepth ? 0.9 : 0.9999;
            }
        }
    });

    /**
     * Determines if a compressed texture format is supported.
     * @param {String} format The texture format. May be the name of the format or the WebGL extension name, e.g. s3tc or WEBGL_compressed_texture_s3tc.
     * @return {boolean} Whether or not the format is supported.
     */
    Scene.prototype.getCompressedTextureFormatSupported = function(format) {
        var context = this.context;
        return ((format === 'WEBGL_compressed_texture_s3tc' || format === 's3tc') && context.s3tc) ||
               ((format === 'WEBGL_compressed_texture_pvrtc' || format === 'pvrtc') && context.pvrtc) ||
               ((format === 'WEBGL_compressed_texture_etc1' || format === 'etc1') && context.etc1);
    };

    function updateDerivedCommands(scene, command, shadowsDirty) {
        var frameState = scene._frameState;
        var context = scene._context;
        var oit = scene._view.oit;
        var lightShadowMaps = frameState.shadowState.lightShadowMaps;
        var lightShadowsEnabled = frameState.shadowState.lightShadowsEnabled;

        var derivedCommands = command.derivedCommands;

        if (defined(command.pickId)) {
            derivedCommands.picking = DerivedCommand.createPickDerivedCommand(scene, command, context, derivedCommands.picking);
        }

        if (!command.pickOnly) {
            derivedCommands.depth = DerivedCommand.createDepthOnlyDerivedCommand(scene, command, context, derivedCommands.depth);
        }

        derivedCommands.originalCommand = command;

        if (scene._hdr) {
            derivedCommands.hdr = DerivedCommand.createHdrCommand(command, context, derivedCommands.hdr);
            command = derivedCommands.hdr.command;
            derivedCommands = command.derivedCommands;
        }

        if (lightShadowsEnabled && command.receiveShadows) {
            derivedCommands.shadows = ShadowMap.createReceiveDerivedCommand(lightShadowMaps, command, shadowsDirty, context, derivedCommands.shadows);
        }

        if (command.pass === Pass.TRANSLUCENT && defined(oit) && oit.isSupported()) {
            if (lightShadowsEnabled && command.receiveShadows) {
                derivedCommands.oit = defined(derivedCommands.oit) ? derivedCommands.oit : {};
                derivedCommands.oit.shadows = oit.createDerivedCommands(derivedCommands.shadows.receiveCommand, context, derivedCommands.oit.shadows);
            } else {
                derivedCommands.oit = oit.createDerivedCommands(command, context, derivedCommands.oit);
            }
        }
    }

    /**
     * @private
     */
    Scene.prototype.updateDerivedCommands = function(command) {
        if (!defined(command.derivedCommands)) {
            // Is not a DrawCommand
            return;
        }

        var frameState = this._frameState;
        var context = this._context;

        // Update derived commands when any shadow maps become dirty
        var shadowsDirty = false;
        var lastDirtyTime = frameState.shadowState.lastDirtyTime;
        if (command.lastDirtyTime !== lastDirtyTime) {
            command.lastDirtyTime = lastDirtyTime;
            command.dirty = true;
            shadowsDirty = true;
        }

        var useLogDepth = frameState.useLogDepth;
        var useHdr = this._hdr;
        var derivedCommands = command.derivedCommands;
        var hasLogDepthDerivedCommands = defined(derivedCommands.logDepth);
        var hasHdrCommands = defined(derivedCommands.hdr);
        var hasDerivedCommands = defined(derivedCommands.originalCommand);
        var needsLogDepthDerivedCommands = useLogDepth && !hasLogDepthDerivedCommands;
        var needsHdrCommands = useHdr && !hasHdrCommands;
        var needsDerivedCommands = (!useLogDepth || !useHdr) && !hasDerivedCommands;
        command.dirty = command.dirty || needsLogDepthDerivedCommands || needsHdrCommands || needsDerivedCommands;

        if (command.dirty) {
            command.dirty = false;

            var shadowMaps = frameState.shadowState.shadowMaps;
            var shadowsEnabled = frameState.shadowState.shadowsEnabled;
            if (shadowsEnabled && command.castShadows) {
                derivedCommands.shadows = ShadowMap.createCastDerivedCommand(shadowMaps, command, shadowsDirty, context, derivedCommands.shadows);
            }

            if (hasLogDepthDerivedCommands || needsLogDepthDerivedCommands) {
                derivedCommands.logDepth = DerivedCommand.createLogDepthCommand(command, context, derivedCommands.logDepth);
                updateDerivedCommands(this, derivedCommands.logDepth.command, shadowsDirty);
            }
            if (hasDerivedCommands || needsDerivedCommands) {
                updateDerivedCommands(this, command, shadowsDirty);
            }
        }
    };

    var renderTilesetPassState = new Cesium3DTilePassState({
        pass : Cesium3DTilePass.RENDER
    });

    var preloadTilesetPassState = new Cesium3DTilePassState({
        pass : Cesium3DTilePass.PRELOAD
    });

    var preloadFlightTilesetPassState = new Cesium3DTilePassState({
        pass : Cesium3DTilePass.PRELOAD_FLIGHT
    });

    var requestRenderModeDeferCheckPassState = new Cesium3DTilePassState({
        pass : Cesium3DTilePass.REQUEST_RENDER_MODE_DEFER_CHECK
    });

    var scratchOccluderBoundingSphere = new BoundingSphere();
    var scratchOccluder;

    function getOccluder(scene) {
        // TODO: The occluder is the top-level globe. When we add
        //       support for multiple central bodies, this should be the closest one.
        var globe = scene.globe;
        if (scene._mode === SceneMode.SCENE3D && defined(globe) && globe.show) {
            var ellipsoid = globe.ellipsoid;
            var minimumTerrainHeight = scene.frameState.minimumTerrainHeight;
            scratchOccluderBoundingSphere.radius = ellipsoid.minimumRadius + minimumTerrainHeight;
            scratchOccluder = Occluder.fromBoundingSphere(scratchOccluderBoundingSphere, scene.camera.positionWC, scratchOccluder);
            return scratchOccluder;
        }

        return undefined;
    }

    /**
     * @private
     */
    Scene.prototype.clearPasses = function(passes) {
        passes.render = false;
        passes.pick = false;
        passes.depth = false;
        passes.postProcess = false;
        passes.offscreen = false;
    };

    function updateFrameNumber(scene, frameNumber, time) {
        var frameState = scene._frameState;
        frameState.frameNumber = frameNumber;
        frameState.time = JulianDate.clone(time, frameState.time);
    }

    /**
     * @private
     */
    Scene.prototype.updateFrameState = function() {
        var camera = this.camera;

        var frameState = this._frameState;
        frameState.commandList.length = 0;
        frameState.shadowMaps.length = 0;
        frameState.brdfLutGenerator = this._brdfLutGenerator;
        frameState.environmentMap = this.skyBox && this.skyBox._cubeMap;
        frameState.mode = this._mode;
        frameState.morphTime = this.morphTime;
        frameState.mapProjection = this.mapProjection;
        frameState.camera = camera;
        frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
        frameState.occluder = getOccluder(this);
        frameState.terrainExaggeration = this._terrainExaggeration;
        frameState.minimumTerrainHeight = 0.0;
        frameState.minimumDisableDepthTestDistance = this._minimumDisableDepthTestDistance;
        frameState.invertClassification = this.invertClassification;
        frameState.useLogDepth = this._logDepthBuffer && !(this.camera.frustum instanceof OrthographicFrustum || this.camera.frustum instanceof OrthographicOffCenterFrustum);
        frameState.light = this.light;

        if (defined(this._specularEnvironmentMapAtlas) && this._specularEnvironmentMapAtlas.ready) {
            frameState.specularEnvironmentMaps = this._specularEnvironmentMapAtlas.texture;
            frameState.specularEnvironmentMapsMaximumLOD = this._specularEnvironmentMapAtlas.maximumMipmapLevel;
        } else {
            frameState.specularEnvironmentMaps = undefined;
            frameState.specularEnvironmentMapsMaximumLOD = undefined;
        }

        frameState.sphericalHarmonicCoefficients = this.sphericalHarmonicCoefficients;

        this._actualInvertClassificationColor = Color.clone(this.invertClassificationColor, this._actualInvertClassificationColor);
        if (!InvertClassification.isTranslucencySupported(this._context)) {
            this._actualInvertClassificationColor.alpha = 1.0;
        }

        frameState.invertClassificationColor = this._actualInvertClassificationColor;

        if (defined(this.globe)) {
            frameState.maximumScreenSpaceError = this.globe.maximumScreenSpaceError;
        } else {
            frameState.maximumScreenSpaceError = 2;
        }

        this.clearPasses(frameState.passes);

        frameState.tilesetPassState = undefined;
    };

    /**
     * @private
     */
    Scene.prototype.isVisible = function(command, cullingVolume, occluder) {
        return ((defined(command)) &&
                ((!defined(command.boundingVolume)) ||
                 !command.cull ||
                 ((cullingVolume.computeVisibility(command.boundingVolume) !== Intersect.OUTSIDE) &&
                  (!defined(occluder) || !command.occlude || !command.boundingVolume.isOccluded(occluder)))));
    };

    function getAttributeLocations(shaderProgram) {
        var attributeLocations = {};
        var attributes = shaderProgram.vertexAttributes;
        for (var a in attributes) {
            if (attributes.hasOwnProperty(a)) {
                attributeLocations[a] = attributes[a].index;
            }
        }

        return attributeLocations;
    }

    function createDebugFragmentShaderProgram(command, scene, shaderProgram) {
        var context = scene.context;
        var sp = defaultValue(shaderProgram, command.shaderProgram);
        var fs = sp.fragmentShaderSource.clone();

        var targets = [];
        fs.sources = fs.sources.map(function(source) {
            source = ShaderSource.replaceMain(source, 'czm_Debug_main');
            var re = /gl_FragData\[(\d+)\]/g;
            var match;
            while ((match = re.exec(source)) !== null) {
                if (targets.indexOf(match[1]) === -1) {
                    targets.push(match[1]);
                }
            }
            return source;
        });
        var length = targets.length;

        var newMain =
            'void main() \n' +
            '{ \n' +
            '    czm_Debug_main(); \n';

        var i;
        if (scene.debugShowCommands) {
            if (!defined(command._debugColor)) {
                command._debugColor = Color.fromRandom();
            }
            var c = command._debugColor;
            if (length > 0) {
                for (i = 0; i < length; ++i) {
                    newMain += '    gl_FragData[' + targets[i] + '].rgb *= vec3(' + c.red + ', ' + c.green + ', ' + c.blue + '); \n';
                }
            } else {
                newMain += '    ' + 'gl_FragColor' + '.rgb *= vec3(' + c.red + ', ' + c.green + ', ' + c.blue + '); \n';
            }
        }

        if (scene.debugShowFrustums) {
            // Support up to three frustums.  If a command overlaps all
            // three, it's code is not changed.
            var r = (command.debugOverlappingFrustums & (1 << 0)) ? '1.0' : '0.0';
            var g = (command.debugOverlappingFrustums & (1 << 1)) ? '1.0' : '0.0';
            var b = (command.debugOverlappingFrustums & (1 << 2)) ? '1.0' : '0.0';
            if (length > 0) {
                for (i = 0; i < length; ++i) {
                    newMain += '    gl_FragData[' + targets[i] + '].rgb *= vec3(' + r + ', ' + g + ', ' + b + '); \n';
                }
            } else {
                newMain += '    ' + 'gl_FragColor' + '.rgb *= vec3(' + r + ', ' + g + ', ' + b + '); \n';
            }
        }

        newMain += '}';

        fs.sources.push(newMain);

        var attributeLocations = getAttributeLocations(sp);

        return ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : sp.vertexShaderSource,
            fragmentShaderSource : fs,
            attributeLocations : attributeLocations
        });
    }

    function executeDebugCommand(command, scene, passState) {
        var debugCommand = DrawCommand.shallowClone(command);
        debugCommand.shaderProgram = createDebugFragmentShaderProgram(command, scene);
        debugCommand.execute(scene.context, passState);
        debugCommand.shaderProgram.destroy();
    }

    var transformFrom2D = new Matrix4(0.0, 0.0, 1.0, 0.0,
                                      1.0, 0.0, 0.0, 0.0,
                                      0.0, 1.0, 0.0, 0.0,
                                      0.0, 0.0, 0.0, 1.0);
    transformFrom2D = Matrix4.inverseTransformation(transformFrom2D, transformFrom2D);

    function debugShowBoundingVolume(command, scene, passState, debugFramebuffer) {
        // Debug code to draw bounding volume for command.  Not optimized!
        // Assumes bounding volume is a bounding sphere or box
        var frameState = scene._frameState;
        var context = frameState.context;
        var boundingVolume = command.boundingVolume;

        if (defined(scene._debugVolume)) {
            scene._debugVolume.destroy();
        }

        var geometry;

        var center = Cartesian3.clone(boundingVolume.center);
        if (frameState.mode !== SceneMode.SCENE3D) {
            center = Matrix4.multiplyByPoint(transformFrom2D, center, center);
            var projection = frameState.mapProjection;
            var centerCartographic = projection.unproject(center);
            center = projection.ellipsoid.cartographicToCartesian(centerCartographic);
        }

        if (defined(boundingVolume.radius)) {
            var radius = boundingVolume.radius;

            geometry = GeometryPipeline.toWireframe(EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
                radii : new Cartesian3(radius, radius, radius),
                vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT
            })));

            scene._debugVolume = new Primitive({
                geometryInstances : new GeometryInstance({
                    geometry : geometry,
                    modelMatrix : Matrix4.fromTranslation(center),
                    attributes : {
                        color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0)
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : false
                }),
                asynchronous : false
            });
        } else {
            var halfAxes = boundingVolume.halfAxes;

            geometry = GeometryPipeline.toWireframe(BoxGeometry.createGeometry(BoxGeometry.fromDimensions({
                dimensions : new Cartesian3(2.0, 2.0, 2.0),
                vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT
            })));

            scene._debugVolume = new Primitive({
                geometryInstances : new GeometryInstance({
                    geometry : geometry,
                    modelMatrix : Matrix4.fromRotationTranslation(halfAxes, center, new Matrix4()),
                    attributes : {
                        color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0)
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : false
                }),
                asynchronous : false
            });
        }

        var savedCommandList = frameState.commandList;
        var commandList = frameState.commandList = [];
        scene._debugVolume.update(frameState);

        command = commandList[0];

        if (frameState.useLogDepth) {
            var logDepth = DerivedCommand.createLogDepthCommand(command, context);
            command = logDepth.command;
        }

        var framebuffer;
        if (defined(debugFramebuffer)) {
            framebuffer = passState.framebuffer;
            passState.framebuffer = debugFramebuffer;
        }

        command.execute(context, passState);

        if (defined(framebuffer)) {
            passState.framebuffer = framebuffer;
        }

        frameState.commandList = savedCommandList;
    }

    function executeCommand(command, scene, context, passState, debugFramebuffer) {
        var frameState = scene._frameState;

        if ((defined(scene.debugCommandFilter)) && !scene.debugCommandFilter(command)) {
            return;
        }

        if (command instanceof ClearCommand) {
            command.execute(context, passState);
            return;
        }

        if (command.debugShowBoundingVolume && (defined(command.boundingVolume))) {
            debugShowBoundingVolume(command, scene, passState, debugFramebuffer);
        }

        if (frameState.useLogDepth && defined(command.derivedCommands.logDepth)) {
            command = command.derivedCommands.logDepth.command;
        }

        var passes = frameState.passes;
        if (!passes.pick && scene._hdr && defined(command.derivedCommands) && defined(command.derivedCommands.hdr)) {
            command = command.derivedCommands.hdr.command;
        }

        if (passes.pick || passes.depth) {
            if (passes.pick && !passes.depth && defined(command.derivedCommands.picking)) {
                command = command.derivedCommands.picking.pickCommand;
                command.execute(context, passState);
                return;
            } else if (defined(command.derivedCommands.depth)) {
                command = command.derivedCommands.depth.depthOnlyCommand;
                command.execute(context, passState);
                return;
            }
        }

        if (scene.debugShowCommands || scene.debugShowFrustums) {
            executeDebugCommand(command, scene, passState);
            return;
        }

        if (frameState.shadowState.lightShadowsEnabled && command.receiveShadows && defined(command.derivedCommands.shadows)) {
            // If the command receives shadows, execute the derived shadows command.
            // Some commands, such as OIT derived commands, do not have derived shadow commands themselves
            // and instead shadowing is built-in. In this case execute the command regularly below.
            command.derivedCommands.shadows.receiveCommand.execute(context, passState);
        } else {
            command.execute(context, passState);
        }
    }

    function executeIdCommand(command, scene, context, passState) {
        var frameState = scene._frameState;
        var derivedCommands = command.derivedCommands;
        if (!defined(derivedCommands)) {
            return;
        }

        if (frameState.useLogDepth && defined(derivedCommands.logDepth)) {
            command = derivedCommands.logDepth.command;
        }

        derivedCommands = command.derivedCommands;
        if (defined(derivedCommands.picking)) {
            command = derivedCommands.picking.pickCommand;
            command.execute(context, passState);
        } else if (defined(derivedCommands.depth)) {
            command = derivedCommands.depth.depthOnlyCommand;
            command.execute(context, passState);
        }
    }

    function backToFront(a, b, position) {
        return b.boundingVolume.distanceSquaredTo(position) - a.boundingVolume.distanceSquaredTo(position);
    }

    function frontToBack(a, b, position) {
        // When distances are equal equal favor sorting b before a. This gives render priority to commands later in the list.
        return a.boundingVolume.distanceSquaredTo(position) - b.boundingVolume.distanceSquaredTo(position) + CesiumMath.EPSILON12;
    }

    function executeTranslucentCommandsBackToFront(scene, executeFunction, passState, commands, invertClassification) {
        var context = scene.context;

        mergeSort(commands, backToFront, scene.camera.positionWC);

        if (defined(invertClassification)) {
            executeFunction(invertClassification.unclassifiedCommand, scene, context, passState);
        }

        var length = commands.length;
        for (var i = 0; i < length; ++i) {
            executeFunction(commands[i], scene, context, passState);
        }
    }

    function executeTranslucentCommandsFrontToBack(scene, executeFunction, passState, commands, invertClassification) {
        var context = scene.context;

        mergeSort(commands, frontToBack, scene.camera.positionWC);

        if (defined(invertClassification)) {
            executeFunction(invertClassification.unclassifiedCommand, scene, context, passState);
        }

        var length = commands.length;
        for (var i = 0; i < length; ++i) {
            executeFunction(commands[i], scene, context, passState);
        }
    }

    function getDebugGlobeDepth(scene, index) {
        var globeDepths = scene._view.debugGlobeDepths;
        var globeDepth = globeDepths[index];
        if (!defined(globeDepth) && scene.context.depthTexture) {
            globeDepth = new GlobeDepth();
            globeDepths[index] = globeDepth;
        }
        return globeDepth;
    }

    var scratchPerspectiveFrustum = new PerspectiveFrustum();
    var scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum();
    var scratchOrthographicFrustum = new OrthographicFrustum();
    var scratchOrthographicOffCenterFrustum = new OrthographicOffCenterFrustum();

    function executeCommands(scene, passState) {
        var camera = scene.camera;
        var context = scene.context;
        var us = context.uniformState;

        us.updateCamera(camera);

        // Create a working frustum from the original camera frustum.
        var frustum;
        if (defined(camera.frustum.fov)) {
            frustum = camera.frustum.clone(scratchPerspectiveFrustum);
        } else if (defined(camera.frustum.infiniteProjectionMatrix)){
            frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum);
        } else if (defined(camera.frustum.width)) {
            frustum = camera.frustum.clone(scratchOrthographicFrustum);
        } else {
            frustum = camera.frustum.clone(scratchOrthographicOffCenterFrustum);
        }

        // Ideally, we would render the sky box and atmosphere last for
        // early-z, but we would have to draw it in each frustum
        frustum.near = camera.frustum.near;
        frustum.far = camera.frustum.far;
        us.updateFrustum(frustum);
        us.updatePass(Pass.ENVIRONMENT);

        var passes = scene._frameState.passes;
        var picking = passes.pick;
        var environmentState = scene._environmentState;
        var view = scene._view;
        var renderTranslucentDepthForPick = environmentState.renderTranslucentDepthForPick;
        var useWebVR = environmentState.useWebVR;

        // Do not render environment primitives during a pick pass since they do not generate picking commands.
        if (!picking) {
            var skyBoxCommand = environmentState.skyBoxCommand;
            if (defined(skyBoxCommand)) {
                executeCommand(skyBoxCommand, scene, context, passState);
            }

            if (environmentState.isSkyAtmosphereVisible) {
                executeCommand(environmentState.skyAtmosphereCommand, scene, context, passState);
            }

            if (environmentState.isSunVisible) {
                environmentState.sunDrawCommand.execute(context, passState);
                if (scene.sunBloom && !useWebVR) {
                    var framebuffer;
                    if (environmentState.useGlobeDepthFramebuffer) {
                        framebuffer = view.globeDepth.framebuffer;
                    } else if (environmentState.usePostProcess) {
                        framebuffer = view.sceneFramebuffer.getFramebuffer();
                    } else {
                        framebuffer = environmentState.originalFramebuffer;
                    }
                    scene._sunPostProcess.execute(context);
                    scene._sunPostProcess.copy(context, framebuffer);
                    passState.framebuffer = framebuffer;
                }
            }

            // Moon can be seen through the atmosphere, since the sun is rendered after the atmosphere.
            if (environmentState.isMoonVisible) {
                environmentState.moonCommand.execute(context, passState);
            }
        }

        // Determine how translucent surfaces will be handled.
        var executeTranslucentCommands;
        if (environmentState.useOIT) {
            if (!defined(scene._executeOITFunction)) {
                scene._executeOITFunction = function(scene, executeFunction, passState, commands, invertClassification) {
                    view.oit.executeCommands(scene, executeFunction, passState, commands, invertClassification);
                };
            }
            executeTranslucentCommands = scene._executeOITFunction;
        } else if (passes.render) {
            executeTranslucentCommands = executeTranslucentCommandsBackToFront;
        } else {
            executeTranslucentCommands = executeTranslucentCommandsFrontToBack;
        }

        var frustumCommandsList = view.frustumCommandsList;
        var numFrustums = frustumCommandsList.length;

        var clearGlobeDepth = environmentState.clearGlobeDepth;
        var useDepthPlane = environmentState.useDepthPlane;
        var separatePrimitiveFramebuffer = environmentState.separatePrimitiveFramebuffer = false;
        var clearDepth = scene._depthClearCommand;
        var clearStencil = scene._stencilClearCommand;
        var clearClassificationStencil = scene._classificationStencilClearCommand;
        var depthPlane = scene._depthPlane;
        var usePostProcessSelected = environmentState.usePostProcessSelected;

        var height2D = camera.position.z;

        // Execute commands in each frustum in back to front order
        var j;
        for (var i = 0; i < numFrustums; ++i) {
            var index = numFrustums - i - 1;
            var frustumCommands = frustumCommandsList[index];

            if (scene.mode === SceneMode.SCENE2D) {
                // To avoid z-fighting in 2D, move the camera to just before the frustum
                // and scale the frustum depth to be in [1.0, nearToFarDistance2D].
                camera.position.z = height2D - frustumCommands.near + 1.0;
                frustum.far = Math.max(1.0, frustumCommands.far - frustumCommands.near);
                frustum.near = 1.0;
                us.update(scene.frameState);
                us.updateFrustum(frustum);
            } else {
                // Avoid tearing artifacts between adjacent frustums in the opaque passes
                frustum.near = index !== 0 ? frustumCommands.near * scene.opaqueFrustumNearOffset : frustumCommands.near;
                frustum.far = frustumCommands.far;
                us.updateFrustum(frustum);
            }

            var globeDepth = scene.debugShowGlobeDepth ? getDebugGlobeDepth(scene, index) : view.globeDepth;

            if (separatePrimitiveFramebuffer) {
                // Render to globe framebuffer in GLOBE pass
                passState.framebuffer = globeDepth.framebuffer;
            }

            var fb;
            if (scene.debugShowGlobeDepth && defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
                globeDepth.update(context, passState, view.viewport, scene._hdr, clearGlobeDepth);
                globeDepth.clear(context, passState, scene._clearColorCommand.color);
                fb = passState.framebuffer;
                passState.framebuffer = globeDepth.framebuffer;
            }

            clearDepth.execute(context, passState);

            if (context.stencilBuffer) {
                clearStencil.execute(context, passState);
            }

            us.updatePass(Pass.GLOBE);
            var commands = frustumCommands.commands[Pass.GLOBE];
            var length = frustumCommands.indices[Pass.GLOBE];
            for (j = 0; j < length; ++j) {
                executeCommand(commands[j], scene, context, passState);
            }

            if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
                globeDepth.executeCopyDepth(context, passState);
            }

            if (scene.debugShowGlobeDepth && defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
                passState.framebuffer = fb;
            }

            // Draw terrain classification
            us.updatePass(Pass.TERRAIN_CLASSIFICATION);
            commands = frustumCommands.commands[Pass.TERRAIN_CLASSIFICATION];
            length = frustumCommands.indices[Pass.TERRAIN_CLASSIFICATION];
            for (j = 0; j < length; ++j) {
                executeCommand(commands[j], scene, context, passState);
            }

            if (clearGlobeDepth) {
                clearDepth.execute(context, passState);
                if (useDepthPlane) {
                    depthPlane.execute(context, passState);
                }
            }

            if (separatePrimitiveFramebuffer) {
                // Render to primitive framebuffer in all other passes
                passState.framebuffer = globeDepth.primitiveFramebuffer;
            }

            if (!environmentState.useInvertClassification || picking) {
                // Common/fastest path. Draw 3D Tiles and classification normally.

                // Draw 3D Tiles
                us.updatePass(Pass.CESIUM_3D_TILE);
                commands = frustumCommands.commands[Pass.CESIUM_3D_TILE];
                length = frustumCommands.indices[Pass.CESIUM_3D_TILE];
                for (j = 0; j < length; ++j) {
                    executeCommand(commands[j], scene, context, passState);
                }

                if (length > 0) {
                    if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
                        globeDepth.executeUpdateDepth(context, passState, clearGlobeDepth);
                    }

                    // Draw classifications. Modifies 3D Tiles color.
                    us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION);
                    commands = frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION];
                    length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION];
                    for (j = 0; j < length; ++j) {
                        executeCommand(commands[j], scene, context, passState);
                    }
                }
            } else {
                // When the invert classification color is opaque:
                //    Main FBO (FBO1):                   Main_Color   + Main_DepthStencil
                //    Invert classification FBO (FBO2) : Invert_Color + Main_DepthStencil
                //
                //    1. Clear FBO2 color to vec4(0.0) for each frustum
                //    2. Draw 3D Tiles to FBO2
                //    3. Draw classification to FBO2
                //    4. Fullscreen pass to FBO1, draw Invert_Color when:
                //           * Main_DepthStencil has the stencil bit set > 0 (classified)
                //    5. Fullscreen pass to FBO1, draw Invert_Color * czm_invertClassificationColor when:
                //           * Main_DepthStencil has stencil bit set to 0 (unclassified) and
                //           * Invert_Color !== vec4(0.0)
                //
                // When the invert classification color is translucent:
                //    Main FBO (FBO1):                  Main_Color         + Main_DepthStencil
                //    Invert classification FBO (FBO2): Invert_Color       + Invert_DepthStencil
                //    IsClassified FBO (FBO3):          IsClassified_Color + Invert_DepthStencil
                //
                //    1. Clear FBO2 and FBO3 color to vec4(0.0), stencil to 0, and depth to 1.0
                //    2. Draw 3D Tiles to FBO2
                //    3. Draw classification to FBO2
                //    4. Fullscreen pass to FBO3, draw any color when
                //           * Invert_DepthStencil has the stencil bit set > 0 (classified)
                //    5. Fullscreen pass to FBO1, draw Invert_Color when:
                //           * Invert_Color !== vec4(0.0) and
                //           * IsClassified_Color !== vec4(0.0)
                //    6. Fullscreen pass to FBO1, draw Invert_Color * czm_invertClassificationColor when:
                //           * Invert_Color !== vec4(0.0) and
                //           * IsClassified_Color === vec4(0.0)
                //
                // NOTE: Step six when translucent invert color occurs after the TRANSLUCENT pass
                //
                scene._invertClassification.clear(context, passState);

                var opaqueClassificationFramebuffer = passState.framebuffer;
                passState.framebuffer = scene._invertClassification._fbo;

                // Draw normally
                us.updatePass(Pass.CESIUM_3D_TILE);
                commands = frustumCommands.commands[Pass.CESIUM_3D_TILE];
                length = frustumCommands.indices[Pass.CESIUM_3D_TILE];
                for (j = 0; j < length; ++j) {
                    executeCommand(commands[j], scene, context, passState);
                }

                if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
                    globeDepth.executeUpdateDepth(context, passState, clearGlobeDepth);
                }

                // Set stencil
                us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW);
                commands = frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW];
                length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW];
                for (j = 0; j < length; ++j) {
                    executeCommand(commands[j], scene, context, passState);
                }

                passState.framebuffer = opaqueClassificationFramebuffer;

                // Fullscreen pass to copy classified fragments
                scene._invertClassification.executeClassified(context, passState);
                if (scene.frameState.invertClassificationColor.alpha === 1.0) {
                    // Fullscreen pass to copy unclassified fragments when alpha == 1.0
                    scene._invertClassification.executeUnclassified(context, passState);
                }

                // Clear stencil set by the classification for the next classification pass
                if (length > 0 && context.stencilBuffer) {
                    clearClassificationStencil.execute(context, passState);
                }

                // Draw style over classification.
                us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION);
                commands = frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION];
                length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION];
                for (j = 0; j < length; ++j) {
                    executeCommand(commands[j], scene, context, passState);
                }
            }

            if (length > 0 && context.stencilBuffer) {
                clearStencil.execute(context, passState);
            }

            us.updatePass(Pass.OPAQUE);
            commands = frustumCommands.commands[Pass.OPAQUE];
            length = frustumCommands.indices[Pass.OPAQUE];
            for (j = 0; j < length; ++j) {
                executeCommand(commands[j], scene, context, passState);
            }

            if (index !== 0 && scene.mode !== SceneMode.SCENE2D) {
                // Do not overlap frustums in the translucent pass to avoid blending artifacts
                frustum.near = frustumCommands.near;
                us.updateFrustum(frustum);
            }

            var invertClassification;
            if (!picking && environmentState.useInvertClassification && scene.frameState.invertClassificationColor.alpha < 1.0) {
                // Fullscreen pass to copy unclassified fragments when alpha < 1.0.
                // Not executed when undefined.
                invertClassification = scene._invertClassification;
            }

            us.updatePass(Pass.TRANSLUCENT);
            commands = frustumCommands.commands[Pass.TRANSLUCENT];
            commands.length = frustumCommands.indices[Pass.TRANSLUCENT];
            executeTranslucentCommands(scene, executeCommand, passState, commands, invertClassification);

            if (context.depthTexture && scene.useDepthPicking && (environmentState.useGlobeDepthFramebuffer || renderTranslucentDepthForPick)) {
                // PERFORMANCE_IDEA: Use MRT to avoid the extra copy.
                var depthStencilTexture = renderTranslucentDepthForPick ? passState.framebuffer.depthStencilTexture : globeDepth.framebuffer.depthStencilTexture;
                var pickDepth = scene._picking.getPickDepth(scene, index);
                pickDepth.update(context, depthStencilTexture);
                pickDepth.executeCopyDepth(context, passState);
            }

            if (separatePrimitiveFramebuffer) {
                // Reset framebuffer
                passState.framebuffer = globeDepth.framebuffer;
            }

            if (picking || !usePostProcessSelected) {
                continue;
            }

            var originalFramebuffer = passState.framebuffer;
            passState.framebuffer = view.sceneFramebuffer.getIdFramebuffer();

            // reset frustum
            frustum.near = index !== 0 ? frustumCommands.near * scene.opaqueFrustumNearOffset : frustumCommands.near;
            frustum.far = frustumCommands.far;
            us.updateFrustum(frustum);

            us.updatePass(Pass.GLOBE);
            commands = frustumCommands.commands[Pass.GLOBE];
            length = frustumCommands.indices[Pass.GLOBE];
            for (j = 0; j < length; ++j) {
                executeIdCommand(commands[j], scene, context, passState);
            }

            if (clearGlobeDepth) {
                clearDepth.framebuffer = passState.framebuffer;
                clearDepth.execute(context, passState);
                clearDepth.framebuffer = undefined;
            }

            if (clearGlobeDepth && useDepthPlane) {
                depthPlane.execute(context, passState);
            }

            us.updatePass(Pass.CESIUM_3D_TILE);
            commands = frustumCommands.commands[Pass.CESIUM_3D_TILE];
            length = frustumCommands.indices[Pass.CESIUM_3D_TILE];
            for (j = 0; j < length; ++j) {
                executeIdCommand(commands[j], scene, context, passState);
            }

            us.updatePass(Pass.OPAQUE);
            commands = frustumCommands.commands[Pass.OPAQUE];
            length = frustumCommands.indices[Pass.OPAQUE];
            for (j = 0; j < length; ++j) {
                executeIdCommand(commands[j], scene, context, passState);
            }

            us.updatePass(Pass.TRANSLUCENT);
            commands = frustumCommands.commands[Pass.TRANSLUCENT];
            length = frustumCommands.indices[Pass.TRANSLUCENT];
            for (j = 0; j < length; ++j) {
                executeIdCommand(commands[j], scene, context, passState);
            }

            passState.framebuffer = originalFramebuffer;
        }
    }

    function executeComputeCommands(scene) {
        var us = scene.context.uniformState;
        us.updatePass(Pass.COMPUTE);

        var sunComputeCommand = scene._environmentState.sunComputeCommand;
        if (defined(sunComputeCommand)) {
            sunComputeCommand.execute(scene._computeEngine);
        }

        var commandList = scene._computeCommandList;
        var length = commandList.length;
        for (var i = 0; i < length; ++i) {
            commandList[i].execute(scene._computeEngine);
        }
    }

    function executeOverlayCommands(scene, passState) {
        var us = scene.context.uniformState;
        us.updatePass(Pass.OVERLAY);

        var context = scene.context;
        var commandList = scene._overlayCommandList;
        var length = commandList.length;
        for (var i = 0; i < length; ++i) {
            commandList[i].execute(context, passState);
        }
    }

    function insertShadowCastCommands(scene, commandList, shadowMap) {
        var shadowVolume = shadowMap.shadowMapCullingVolume;
        var isPointLight = shadowMap.isPointLight;
        var passes = shadowMap.passes;
        var numberOfPasses = passes.length;

        var length = commandList.length;
        for (var i = 0; i < length; ++i) {
            var command = commandList[i];
            scene.updateDerivedCommands(command);

            if (command.castShadows && (command.pass === Pass.GLOBE || command.pass === Pass.CESIUM_3D_TILE || command.pass === Pass.OPAQUE || command.pass === Pass.TRANSLUCENT)) {
                if (scene.isVisible(command, shadowVolume)) {
                    if (isPointLight) {
                        for (var k = 0; k < numberOfPasses; ++k) {
                            passes[k].commandList.push(command);
                        }
                    } else if (numberOfPasses === 1) {
                        passes[0].commandList.push(command);
                    } else {
                        var wasVisible = false;
                        // Loop over cascades from largest to smallest
                        for (var j = numberOfPasses - 1; j >= 0; --j) {
                            var cascadeVolume = passes[j].cullingVolume;
                            if (scene.isVisible(command, cascadeVolume)) {
                                passes[j].commandList.push(command);
                                wasVisible = true;
                            } else if (wasVisible) {
                                // If it was visible in the previous cascade but now isn't
                                // then there is no need to check any more cascades
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    function executeShadowMapCastCommands(scene) {
        var frameState = scene.frameState;
        var shadowMaps = frameState.shadowState.shadowMaps;
        var shadowMapLength = shadowMaps.length;

        if (!frameState.shadowState.shadowsEnabled) {
            return;
        }

        var context = scene.context;
        var uniformState = context.uniformState;

        for (var i = 0; i < shadowMapLength; ++i) {
            var shadowMap = shadowMaps[i];
            if (shadowMap.outOfView) {
                continue;
            }

            // Reset the command lists
            var j;
            var passes = shadowMap.passes;
            var numberOfPasses = passes.length;
            for (j = 0; j < numberOfPasses; ++j) {
                passes[j].commandList.length = 0;
            }

            // Insert the primitive/model commands into the command lists
            var sceneCommands = scene.frameState.commandList;
            insertShadowCastCommands(scene, sceneCommands, shadowMap);

            for (j = 0; j < numberOfPasses; ++j) {
                var pass = shadowMap.passes[j];
                uniformState.updateCamera(pass.camera);
                shadowMap.updatePass(context, j);
                var numberOfCommands = pass.commandList.length;
                for (var k = 0; k < numberOfCommands; ++k) {
                    var command = pass.commandList[k];
                    // Set the correct pass before rendering into the shadow map because some shaders
                    // conditionally render based on whether the pass is translucent or opaque.
                    uniformState.updatePass(command.pass);
                    executeCommand(command.derivedCommands.shadows.castCommands[i], scene, context, pass.passState);
                }
            }
        }
    }

    var scratchEyeTranslation = new Cartesian3();

    /**
     * @private
     */
    Scene.prototype.updateAndExecuteCommands = function(passState, backgroundColor) {
        var frameState = this._frameState;
        var mode = frameState.mode;
        var useWebVR = this._environmentState.useWebVR;

        if (useWebVR) {
            executeWebVRCommands(this, passState, backgroundColor);
        } else if (mode !== SceneMode.SCENE2D || this._mapMode2D === MapMode2D.ROTATE) {
            executeCommandsInViewport(true, this, passState, backgroundColor);
        } else {
            updateAndClearFramebuffers(this, passState, backgroundColor);
            execute2DViewportCommands(this, passState);
        }
    };

    function executeWebVRCommands(scene, passState, backgroundColor) {
        var view = scene._view;
        var camera = view.camera;
        var environmentState = scene._environmentState;
        var renderTranslucentDepthForPick = environmentState.renderTranslucentDepthForPick;

        updateAndClearFramebuffers(scene, passState, backgroundColor);

        if (!renderTranslucentDepthForPick) {
            updateAndRenderPrimitives(scene);
        }

        view.createPotentiallyVisibleSet(scene);

        if (!renderTranslucentDepthForPick) {
            executeComputeCommands(scene);
            executeShadowMapCastCommands(scene);
        }

        // Based on Calculating Stereo pairs by Paul Bourke
        // http://paulbourke.net/stereographics/stereorender/
        var viewport = passState.viewport;
        viewport.x = 0;
        viewport.y = 0;
        viewport.width = viewport.width * 0.5;

        var savedCamera = Camera.clone(camera, scene._cameraVR);
        savedCamera.frustum = camera.frustum;

        var near = camera.frustum.near;
        var fo = near * defaultValue(scene.focalLength, 5.0);
        var eyeSeparation = defaultValue(scene.eyeSeparation, fo / 30.0);
        var eyeTranslation = Cartesian3.multiplyByScalar(savedCamera.right, eyeSeparation * 0.5, scratchEyeTranslation);

        camera.frustum.aspectRatio = viewport.width / viewport.height;

        var offset = 0.5 * eyeSeparation * near / fo;

        Cartesian3.add(savedCamera.position, eyeTranslation, camera.position);
        camera.frustum.xOffset = offset;

        executeCommands(scene, passState);

        viewport.x = viewport.width;

        Cartesian3.subtract(savedCamera.position, eyeTranslation, camera.position);
        camera.frustum.xOffset = -offset;

        executeCommands(scene, passState);

        Camera.clone(savedCamera, camera);
    }

    var scratch2DViewportCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO);
    var scratch2DViewportMaxCoord = new Cartesian3();
    var scratch2DViewportSavedPosition = new Cartesian3();
    var scratch2DViewportTransform = new Matrix4();
    var scratch2DViewportCameraTransform = new Matrix4();
    var scratch2DViewportEyePoint = new Cartesian3();
    var scratch2DViewportWindowCoords = new Cartesian3();
    var scratch2DViewport = new BoundingRectangle();

    function execute2DViewportCommands(scene, passState) {
        var context = scene.context;
        var frameState = scene.frameState;
        var camera = scene.camera;

        var originalViewport = passState.viewport;
        var viewport = BoundingRectangle.clone(originalViewport, scratch2DViewport);
        passState.viewport = viewport;

        var maxCartographic = scratch2DViewportCartographic;
        var maxCoord = scratch2DViewportMaxCoord;

        var projection = scene.mapProjection;
        projection.project(maxCartographic, maxCoord);

        var position = Cartesian3.clone(camera.position, scratch2DViewportSavedPosition);
        var transform = Matrix4.clone(camera.transform, scratch2DViewportCameraTransform);
        var frustum = camera.frustum.clone();

        camera._setTransform(Matrix4.IDENTITY);

        var viewportTransformation = Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, scratch2DViewportTransform);
        var projectionMatrix = camera.frustum.projectionMatrix;

        var x = camera.positionWC.y;
        var eyePoint = Cartesian3.fromElements(CesiumMath.sign(x) * maxCoord.x - x, 0.0, -camera.positionWC.x, scratch2DViewportEyePoint);
        var windowCoordinates = Transforms.pointToGLWindowCoordinates(projectionMatrix, viewportTransformation, eyePoint, scratch2DViewportWindowCoords);

        windowCoordinates.x = Math.floor(windowCoordinates.x);

        var viewportX = viewport.x;
        var viewportWidth = viewport.width;

        if (x === 0.0 || windowCoordinates.x <= viewportX  || windowCoordinates.x >= viewportX + viewportWidth) {
            executeCommandsInViewport(true, scene, passState);
        } else if (Math.abs(viewportX + viewportWidth * 0.5 - windowCoordinates.x) < 1.0) {
            viewport.width = windowCoordinates.x - viewport.x;

            camera.position.x *= CesiumMath.sign(camera.position.x);

            camera.frustum.right = 0.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(true, scene, passState);

            viewport.x = windowCoordinates.x;

            camera.position.x = -camera.position.x;

            camera.frustum.right = -camera.frustum.left;
            camera.frustum.left = 0.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(false, scene, passState);
        } else if (windowCoordinates.x > viewportX + viewportWidth * 0.5) {
            viewport.width = windowCoordinates.x - viewportX;

            var right = camera.frustum.right;
            camera.frustum.right = maxCoord.x - x;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(true, scene, passState);

            viewport.x = windowCoordinates.x;
            viewport.width = viewportX + viewportWidth - windowCoordinates.x;

            camera.position.x = -camera.position.x;

            camera.frustum.left = -camera.frustum.right;
            camera.frustum.right = right - camera.frustum.right * 2.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(false, scene, passState);
        } else {
            viewport.x = windowCoordinates.x;
            viewport.width = viewportX + viewportWidth - windowCoordinates.x;

            var left = camera.frustum.left;
            camera.frustum.left = -maxCoord.x - x;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(true, scene, passState);

            viewport.x = viewportX;
            viewport.width = windowCoordinates.x - viewportX;

            camera.position.x = -camera.position.x;

            camera.frustum.right = -camera.frustum.left;
            camera.frustum.left = left - camera.frustum.left * 2.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(false, scene, passState);
        }

        camera._setTransform(transform);
        Cartesian3.clone(position, camera.position);
        camera.frustum = frustum.clone();
        passState.viewport = originalViewport;
    }

    function executeCommandsInViewport(firstViewport, scene, passState, backgroundColor) {
        var environmentState = scene._environmentState;
        var view = scene._view;
        var renderTranslucentDepthForPick = environmentState.renderTranslucentDepthForPick;

        if (!firstViewport && !renderTranslucentDepthForPick) {
            scene.frameState.commandList.length = 0;
        }

        if (!renderTranslucentDepthForPick) {
            updateAndRenderPrimitives(scene);
        }

        view.createPotentiallyVisibleSet(scene);

        if (firstViewport) {
            if (defined(backgroundColor)) {
                updateAndClearFramebuffers(scene, passState, backgroundColor);
            }
            if (!renderTranslucentDepthForPick) {
                executeComputeCommands(scene);
                executeShadowMapCastCommands(scene);
            }
        }

        executeCommands(scene, passState);
    }

    var scratchCullingVolume = new CullingVolume();

    /**
     * @private
     */
    Scene.prototype.updateEnvironment = function() {
        var frameState = this._frameState;
        var view = this._view;

        // Update celestial and terrestrial environment effects.
        var environmentState = this._environmentState;
        var renderPass = frameState.passes.render;
        var offscreenPass = frameState.passes.offscreen;
        var skyAtmosphere = this.skyAtmosphere;
        var globe = this.globe;

        if (!renderPass || (this._mode !== SceneMode.SCENE2D && view.camera.frustum instanceof OrthographicFrustum)) {
            environmentState.skyAtmosphereCommand = undefined;
            environmentState.skyBoxCommand = undefined;
            environmentState.sunDrawCommand = undefined;
            environmentState.sunComputeCommand = undefined;
            environmentState.moonCommand = undefined;
        } else {
            if (defined(skyAtmosphere)) {
                if (defined(globe)) {
                    skyAtmosphere.setDynamicAtmosphereColor(globe.enableLighting && globe.dynamicAtmosphereLighting, globe.dynamicAtmosphereLightingFromSun);
                    environmentState.isReadyForAtmosphere = environmentState.isReadyForAtmosphere || globe._surface._tilesToRender.length > 0;
                }
                environmentState.skyAtmosphereCommand = skyAtmosphere.update(frameState);
                if (defined(environmentState.skyAtmosphereCommand)) {
                    this.updateDerivedCommands(environmentState.skyAtmosphereCommand);
                }
            } else {
                environmentState.skyAtmosphereCommand = undefined;
            }

            environmentState.skyBoxCommand = defined(this.skyBox) ? this.skyBox.update(frameState, this._hdr) : undefined;
            var sunCommands = defined(this.sun) ? this.sun.update(frameState, view.passState, this._hdr) : undefined;
            environmentState.sunDrawCommand = defined(sunCommands) ? sunCommands.drawCommand : undefined;
            environmentState.sunComputeCommand = defined(sunCommands) ? sunCommands.computeCommand : undefined;
            environmentState.moonCommand = defined(this.moon) ? this.moon.update(frameState) : undefined;
        }

        var clearGlobeDepth = environmentState.clearGlobeDepth = defined(globe) && (!globe.depthTestAgainstTerrain || this.mode === SceneMode.SCENE2D);
        var useDepthPlane = environmentState.useDepthPlane = clearGlobeDepth && this.mode === SceneMode.SCENE3D;
        if (useDepthPlane) {
            // Update the depth plane that is rendered in 3D when the primitives are
            // not depth tested against terrain so primitives on the backface
            // of the globe are not picked.
            this._depthPlane.update(frameState);
        }

        environmentState.renderTranslucentDepthForPick = false;
        environmentState.useWebVR = this._useWebVR && this.mode !== SceneMode.SCENE2D  && !offscreenPass;

        var occluder = (frameState.mode === SceneMode.SCENE3D) ? frameState.occluder: undefined;
        var cullingVolume = frameState.cullingVolume;

        // get user culling volume minus the far plane.
        var planes = scratchCullingVolume.planes;
        for (var k = 0; k < 5; ++k) {
            planes[k] = cullingVolume.planes[k];
        }
        cullingVolume = scratchCullingVolume;

        // Determine visibility of celestial and terrestrial environment effects.
        environmentState.isSkyAtmosphereVisible = defined(environmentState.skyAtmosphereCommand) && environmentState.isReadyForAtmosphere;
        environmentState.isSunVisible = this.isVisible(environmentState.sunDrawCommand, cullingVolume, occluder);
        environmentState.isMoonVisible = this.isVisible(environmentState.moonCommand, cullingVolume, occluder);

        var envMaps = this.specularEnvironmentMaps;
        var envMapAtlas = this._specularEnvironmentMapAtlas;
        if (defined(envMaps) && (!defined(envMapAtlas) || envMapAtlas.url !== envMaps)) {
            envMapAtlas = envMapAtlas && envMapAtlas.destroy();
            this._specularEnvironmentMapAtlas = new OctahedralProjectedCubeMap(envMaps);
        } else if (!defined(envMaps) && defined(envMapAtlas)) {
            envMapAtlas.destroy();
            this._specularEnvironmentMapAtlas = undefined;
        }

        if (defined(this._specularEnvironmentMapAtlas)) {
            this._specularEnvironmentMapAtlas.update(frameState);
        }
    };

    function updateDebugFrustumPlanes(scene) {
        var frameState = scene._frameState;
        if (scene.debugShowFrustumPlanes !== scene._debugShowFrustumPlanes) {
            if (scene.debugShowFrustumPlanes) {
                scene._debugFrustumPlanes = new DebugCameraPrimitive({
                    camera: scene.camera,
                    updateOnChange: false
                });
            } else {
                scene._debugFrustumPlanes = scene._debugFrustumPlanes && scene._debugFrustumPlanes.destroy();
            }
            scene._debugShowFrustumPlanes = scene.debugShowFrustumPlanes;
        }

        if (defined(scene._debugFrustumPlanes)) {
            scene._debugFrustumPlanes.update(frameState);
        }
    }

    function updateShadowMaps(scene) {
        var frameState = scene._frameState;
        var shadowMaps = frameState.shadowMaps;
        var length = shadowMaps.length;

        var shadowsEnabled = (length > 0) && !frameState.passes.pick && (scene.mode === SceneMode.SCENE3D);
        if (shadowsEnabled !== frameState.shadowState.shadowsEnabled) {
            // Update derived commands when shadowsEnabled changes
            ++frameState.shadowState.lastDirtyTime;
            frameState.shadowState.shadowsEnabled = shadowsEnabled;
        }

        frameState.shadowState.lightShadowsEnabled = false;

        if (!shadowsEnabled) {
            return;
        }

        // Check if the shadow maps are different than the shadow maps last frame.
        // If so, the derived commands need to be updated.
        for (var j = 0; j < length; ++j) {
            if (shadowMaps[j] !== frameState.shadowState.shadowMaps[j]) {
                ++frameState.shadowState.lastDirtyTime;
                break;
            }
        }

        frameState.shadowState.shadowMaps.length = 0;
        frameState.shadowState.lightShadowMaps.length = 0;

        for (var i = 0; i < length; ++i) {
            var shadowMap = shadowMaps[i];
            shadowMap.update(frameState);

            frameState.shadowState.shadowMaps.push(shadowMap);

            if (shadowMap.fromLightSource) {
                frameState.shadowState.lightShadowMaps.push(shadowMap);
                frameState.shadowState.lightShadowsEnabled = true;
            }

            if (shadowMap.dirty) {
                ++frameState.shadowState.lastDirtyTime;
                shadowMap.dirty = false;
            }
        }
    }

    function updateAndRenderPrimitives(scene) {
        var frameState = scene._frameState;

        scene._groundPrimitives.update(frameState);
        scene._primitives.update(frameState);

        updateDebugFrustumPlanes(scene);
        updateShadowMaps(scene);

        if (scene._globe) {
            scene._globe.render(frameState);
        }
    }

    function updateAndClearFramebuffers(scene, passState, clearColor) {
        var context = scene._context;
        var frameState = scene._frameState;
        var environmentState = scene._environmentState;
        var view = scene._view;

        var passes = scene._frameState.passes;
        var picking = passes.pick;
        var useWebVR = environmentState.useWebVR;

        // Preserve the reference to the original framebuffer.
        environmentState.originalFramebuffer = passState.framebuffer;

        // Manage sun bloom post-processing effect.
        if (defined(scene.sun) && scene.sunBloom !== scene._sunBloom) {
            if (scene.sunBloom && !useWebVR) {
                scene._sunPostProcess = new SunPostProcess();
            } else if(defined(scene._sunPostProcess)){
                scene._sunPostProcess = scene._sunPostProcess.destroy();
            }

            scene._sunBloom = scene.sunBloom;
        } else if (!defined(scene.sun) && defined(scene._sunPostProcess)) {
            scene._sunPostProcess = scene._sunPostProcess.destroy();
            scene._sunBloom = false;
        }

        // Clear the pass state framebuffer.
        var clear = scene._clearColorCommand;
        Color.clone(clearColor, clear.color);
        clear.execute(context, passState);

        // Update globe depth rendering based on the current context and clear the globe depth framebuffer.
        // Globe depth is copied for the pick pass to support picking batched geometries in GroundPrimitives.
        var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer = defined(view.globeDepth);
        if (useGlobeDepthFramebuffer) {
            view.globeDepth.update(context, passState, view.viewport, scene._hdr, environmentState.clearGlobeDepth);
            view.globeDepth.clear(context, passState, clearColor);
        }

        // If supported, configure OIT to use the globe depth framebuffer and clear the OIT framebuffer.
        var oit = view.oit;
        var useOIT = environmentState.useOIT = !picking && defined(oit) && oit.isSupported();
        if (useOIT) {
            oit.update(context, passState, view.globeDepth.framebuffer, scene._hdr);
            oit.clear(context, passState, clearColor);
            environmentState.useOIT = oit.isSupported();
        }

        var postProcess = scene.postProcessStages;
        var usePostProcess = environmentState.usePostProcess = !picking && (scene._hdr || postProcess.length > 0 || postProcess.ambientOcclusion.enabled || postProcess.fxaa.enabled || postProcess.bloom.enabled);
        environmentState.usePostProcessSelected = false;
        if (usePostProcess) {
            view.sceneFramebuffer.update(context, view.viewport, scene._hdr);
            view.sceneFramebuffer.clear(context, passState, clearColor);

            postProcess.update(context, frameState.useLogDepth, scene._hdr);
            postProcess.clear(context);

            usePostProcess = environmentState.usePostProcess = postProcess.ready;
            environmentState.usePostProcessSelected = usePostProcess && postProcess.hasSelected;
        }

        if (environmentState.isSunVisible && scene.sunBloom && !useWebVR) {
            passState.framebuffer = scene._sunPostProcess.update(passState);
            scene._sunPostProcess.clear(context, passState, clearColor);
        } else if (useGlobeDepthFramebuffer) {
            passState.framebuffer = view.globeDepth.framebuffer;
        } else if (usePostProcess) {
            passState.framebuffer = view.sceneFramebuffer.getFramebuffer();
        }

        if (defined(passState.framebuffer)) {
            clear.execute(context, passState);
        }

        var useInvertClassification = environmentState.useInvertClassification = !picking && defined(passState.framebuffer) && scene.invertClassification;
        if (useInvertClassification) {
            var depthFramebuffer;
            if (scene.frameState.invertClassificationColor.alpha === 1.0) {
                if (environmentState.useGlobeDepthFramebuffer) {
                    depthFramebuffer = view.globeDepth.framebuffer;
                }
            }

            if (defined(depthFramebuffer) || context.depthTexture) {
                scene._invertClassification.previousFramebuffer = depthFramebuffer;
                scene._invertClassification.update(context);
                scene._invertClassification.clear(context, passState);

                if (scene.frameState.invertClassificationColor.alpha < 1.0 && useOIT) {
                    var command = scene._invertClassification.unclassifiedCommand;
                    var derivedCommands = command.derivedCommands;
                    derivedCommands.oit = oit.createDerivedCommands(command, context, derivedCommands.oit);
                }
            } else {
                environmentState.useInvertClassification = false;
            }
        }
    }

    /**
     * @private
     */
    Scene.prototype.resolveFramebuffers = function(passState) {
        var context = this._context;
        var frameState = this._frameState;
        var environmentState = this._environmentState;
        var view = this._view;
        var globeDepth = view.globeDepth;

        var useOIT = environmentState.useOIT;
        var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer;
        var usePostProcess = environmentState.usePostProcess;

        var defaultFramebuffer = environmentState.originalFramebuffer;
        var globeFramebuffer = useGlobeDepthFramebuffer ? globeDepth.framebuffer : undefined;
        var sceneFramebuffer = view.sceneFramebuffer.getFramebuffer();
        var idFramebuffer = view.sceneFramebuffer.getIdFramebuffer();

        if (environmentState.separatePrimitiveFramebuffer) {
            // Merge primitive framebuffer into globe framebuffer
            globeDepth.executeMergeColor(context, passState);
        }

        if (useOIT) {
            passState.framebuffer = usePostProcess ? sceneFramebuffer : defaultFramebuffer;
            view.oit.execute(context, passState);
        }

        if (usePostProcess) {
            var inputFramebuffer = sceneFramebuffer;
            if (useGlobeDepthFramebuffer && !useOIT) {
                inputFramebuffer = globeFramebuffer;
            }

            var postProcess = this.postProcessStages;
            var colorTexture = inputFramebuffer.getColorTexture(0);
            var idTexture = idFramebuffer.getColorTexture(0);
            var depthTexture = defaultValue(globeFramebuffer, sceneFramebuffer).depthStencilTexture;
            postProcess.execute(context, colorTexture, depthTexture, idTexture);
            postProcess.copy(context, defaultFramebuffer);
        }

        if (!useOIT && !usePostProcess && useGlobeDepthFramebuffer) {
            passState.framebuffer = defaultFramebuffer;
            globeDepth.executeCopyColor(context, passState);
        }

        var useLogDepth = frameState.useLogDepth;

        if (this.debugShowGlobeDepth && useGlobeDepthFramebuffer) {
            var gd = getDebugGlobeDepth(this, this.debugShowDepthFrustum - 1);
            gd.executeDebugGlobeDepth(context, passState, useLogDepth);
        }

        if (this.debugShowPickDepth && useGlobeDepthFramebuffer) {
            var pd = this._picking.getPickDepth(this, this.debugShowDepthFrustum - 1);
            pd.executeDebugPickDepth(context, passState, useLogDepth);
        }
    };

    function callAfterRenderFunctions(scene) {
        // Functions are queued up during primitive update and executed here in case
        // the function modifies scene state that should remain constant over the frame.
        var functions = scene._frameState.afterRender;
        for (var i = 0, length = functions.length; i < length; ++i) {
            functions[i]();
            scene.requestRender();
        }

        functions.length = 0;
    }

    /**
     * @private
     */
    Scene.prototype.initializeFrame = function() {
        // Destroy released shaders and textures once every 120 frames to avoid thrashing the cache
        if (this._shaderFrameCount++ === 120) {
            this._shaderFrameCount = 0;
            this._context.shaderCache.destroyReleasedShaderPrograms();
            this._context.textureCache.destroyReleasedTextures();
        }

        this._tweens.update();

        this._screenSpaceCameraController.update();
        if (defined(this._deviceOrientationCameraController)) {
            this._deviceOrientationCameraController.update();
        }

        this.camera.update(this._mode);
        this.camera._updateCameraChanged();
    };

    function updateDebugShowFramesPerSecond(scene, renderedThisFrame) {
        if (scene.debugShowFramesPerSecond) {
            if (!defined(scene._performanceDisplay)) {
                var performanceContainer = document.createElement('div');
                performanceContainer.className = 'cesium-performanceDisplay-defaultContainer';
                var container = scene._canvas.parentNode;
                container.appendChild(performanceContainer);
                var performanceDisplay = new PerformanceDisplay({container: performanceContainer});
                scene._performanceDisplay = performanceDisplay;
                scene._performanceContainer = performanceContainer;
            }

            scene._performanceDisplay.throttled = scene.requestRenderMode;
            scene._performanceDisplay.update(renderedThisFrame);
        } else if (defined(scene._performanceDisplay)) {
            scene._performanceDisplay = scene._performanceDisplay && scene._performanceDisplay.destroy();
            scene._performanceContainer.parentNode.removeChild(scene._performanceContainer);
        }
    }

    function prePassesUpdate(scene) {
        scene._jobScheduler.resetBudgets();

        var frameState = scene._frameState;
        var primitives = scene.primitives;
        primitives.prePassesUpdate(frameState);

        if (defined(scene.globe)) {
            scene.globe.update(frameState);
        }

        scene._picking.update();
        frameState.creditDisplay.update();
    }

    function postPassesUpdate(scene) {
        var frameState = scene._frameState;
        var primitives = scene.primitives;
        primitives.postPassesUpdate(frameState);

        RequestScheduler.update();
    }

    var scratchBackgroundColor = new Color();

    function render(scene) {
        var frameState = scene._frameState;

        var context = scene.context;
        var us = context.uniformState;

        var view = scene._defaultView;
        scene._view = view;

        scene.updateFrameState();
        frameState.passes.render = true;
        frameState.passes.postProcess = scene.postProcessStages.hasSelected;
        frameState.tilesetPassState = renderTilesetPassState;

        var backgroundColor = defaultValue(scene.backgroundColor, Color.BLACK);
        if (scene._hdr) {
            backgroundColor = Color.clone(backgroundColor, scratchBackgroundColor);
            backgroundColor.red = Math.pow(backgroundColor.red, scene.gamma);
            backgroundColor.green = Math.pow(backgroundColor.green, scene.gamma);
            backgroundColor.blue = Math.pow(backgroundColor.blue, scene.gamma);
        }
        frameState.backgroundColor = backgroundColor;

        scene.fog.update(frameState);

        us.update(frameState);

        var shadowMap = scene.shadowMap;
        if (defined(shadowMap) && shadowMap.enabled) {
            if (!defined(scene.light) || scene.light instanceof SunLight) {
                // Negate the sun direction so that it is from the Sun, not to the Sun
                Cartesian3.negate(us.sunDirectionWC, scene._shadowMapCamera.direction);
            } else {
                Cartesian3.clone(scene.light.direction, scene._shadowMapCamera.direction);
            }
            frameState.shadowMaps.push(shadowMap);
        }

        scene._computeCommandList.length = 0;
        scene._overlayCommandList.length = 0;

        var viewport = view.viewport;
        viewport.x = 0;
        viewport.y = 0;
        viewport.width = context.drawingBufferWidth;
        viewport.height = context.drawingBufferHeight;

        var passState = view.passState;
        passState.framebuffer = undefined;
        passState.blendingEnabled = undefined;
        passState.scissorTest = undefined;
        passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);

        if (defined(scene.globe)) {
            scene.globe.beginFrame(frameState);
        }

        scene.updateEnvironment();
        scene.updateAndExecuteCommands(passState, backgroundColor);
        scene.resolveFramebuffers(passState);

        passState.framebuffer = undefined;
        executeOverlayCommands(scene, passState);

        if (defined(scene.globe)) {
            scene.globe.endFrame(frameState);

            if (!scene.globe.tilesLoaded) {
                scene._renderRequested = true;
            }
        }

        context.endFrame();
    }

    function tryAndCatchError(scene, functionToExecute) {
        try {
            functionToExecute(scene);
        } catch (error) {
            scene._renderError.raiseEvent(scene, error);

            if (scene.rethrowRenderErrors) {
                throw error;
            }
        }
    }

    function updateMostDetailedRayPicks(scene) {
        return scene._picking.updateMostDetailedRayPicks(scene);
    }

    /**
     * Update and render the scene.
     * @param {JulianDate} [time] The simulation time at which to render.
     *
     * @private
     */
    Scene.prototype.render = function(time) {
        /**
         *
         * Pre passes update. Execute any pass invariant code that should run before the passes here.
         *
         */
        this._preUpdate.raiseEvent(this, time);

        var frameState = this._frameState;
        frameState.newFrame = false;

        if (!defined(time)) {
            time = JulianDate.now();
        }

        // Determine if shouldRender
        var cameraChanged = this._view.checkForCameraUpdates(this);
        var shouldRender = !this.requestRenderMode || this._renderRequested || cameraChanged || this._logDepthBufferDirty || this._hdrDirty || (this.mode === SceneMode.MORPHING);
        if (!shouldRender && defined(this.maximumRenderTimeChange) && defined(this._lastRenderTime)) {
            var difference = Math.abs(JulianDate.secondsDifference(this._lastRenderTime, time));
            shouldRender = shouldRender || difference > this.maximumRenderTimeChange;
        }

        if (shouldRender) {
            this._lastRenderTime = JulianDate.clone(time, this._lastRenderTime);
            this._renderRequested = false;
            this._logDepthBufferDirty = false;
            this._hdrDirty = false;

            var frameNumber = CesiumMath.incrementWrap(frameState.frameNumber, 15000000.0, 1.0);
            updateFrameNumber(this, frameNumber, time);
            frameState.newFrame = true;
        }

        tryAndCatchError(this, prePassesUpdate);

        /**
         *
         * Passes update. Add any passes here
         *
         */
        if (this.primitives.show) {
            tryAndCatchError(this, updateMostDetailedRayPicks);
            tryAndCatchError(this, updatePreloadPass);
            tryAndCatchError(this, updatePreloadFlightPass);
            if (!shouldRender) {
                tryAndCatchError(this, updateRequestRenderModeDeferCheckPass);
            }
        }

        this._postUpdate.raiseEvent(this, time);

        if (shouldRender) {
            this._preRender.raiseEvent(this, time);
            frameState.creditDisplay.beginFrame();
            tryAndCatchError(this, render);
        }

        /**
         *
         * Post passes update. Execute any pass invariant code that should run after the passes here.
         *
         */
        updateDebugShowFramesPerSecond(this, shouldRender);
        tryAndCatchError(this, postPassesUpdate);

        // Often used to trigger events (so don't want in trycatch) that the user might be subscribed to. Things like the tile load events, ready promises, etc.
        // We don't want those events to resolve during the render loop because the events might add new primitives
        callAfterRenderFunctions(this);

        if (shouldRender) {
            this._postRender.raiseEvent(this, time);
            frameState.creditDisplay.endFrame();
        }
    };

    /**
     * Update and render the scene. Always forces a new render frame regardless of whether a render was
     * previously requested.
     * @param {JulianDate} [time] The simulation time at which to render.
     *
     * @private
     */
    Scene.prototype.forceRender = function(time) {
        this._renderRequested = true;
        this.render(time);
    };

    /**
     * Requests a new rendered frame when {@link Scene#requestRenderMode} is set to <code>true</code>.
     * The render rate will not exceed the {@link CesiumWidget#targetFrameRate}.
     *
     * @see Scene#requestRenderMode
     */
    Scene.prototype.requestRender = function() {
        this._renderRequested = true;
    };

    /**
     * @private
     */
    Scene.prototype.clampLineWidth = function(width) {
        return Math.max(ContextLimits.minimumAliasedLineWidth, Math.min(width, ContextLimits.maximumAliasedLineWidth));
    };

    /**
     * Returns an object with a `primitive` property that contains the first (top) primitive in the scene
     * at a particular window coordinate or undefined if nothing is at the location. Other properties may
     * potentially be set depending on the type of primitive and may be used to further identify the picked object.
     * <p>
     * When a feature of a 3D Tiles tileset is picked, <code>pick</code> returns a {@link Cesium3DTileFeature} object.
     * </p>
     *
     * @example
     * // On mouse over, color the feature yellow.
     * handler.setInputAction(function(movement) {
     *     var feature = scene.pick(movement.endPosition);
     *     if (feature instanceof Cesium.Cesium3DTileFeature) {
     *         feature.color = Cesium.Color.YELLOW;
     *     }
     * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
     *
     * @param {Cartesian2} windowPosition Window coordinates to perform picking on.
     * @param {Number} [width=3] Width of the pick rectangle.
     * @param {Number} [height=3] Height of the pick rectangle.
     * @returns {Object} Object containing the picked primitive.
     */
    Scene.prototype.pick = function(windowPosition, width, height) {
        return this._picking.pick(this, windowPosition, width, height);
    };

    /**
     * Returns the cartesian position reconstructed from the depth buffer and window position.
     * The returned position is in world coordinates. Used internally by camera functions to
     * prevent conversion to projected 2D coordinates and then back.
     * <p>
     * Set {@link Scene#pickTranslucentDepth} to <code>true</code> to include the depth of
     * translucent primitives; otherwise, this essentially picks through translucent primitives.
     * </p>
     *
     * @private
     *
     * @param {Cartesian2} windowPosition Window coordinates to perform picking on.
     * @param {Cartesian3} [result] The object on which to restore the result.
     * @returns {Cartesian3} The cartesian position in world coordinates.
     *
     * @exception {DeveloperError} Picking from the depth buffer is not supported. Check pickPositionSupported.
     */
    Scene.prototype.pickPositionWorldCoordinates = function(windowPosition, result) {
        return this._picking.pickPositionWorldCoordinates(this, windowPosition, result);
    };

    /**
     * Returns the cartesian position reconstructed from the depth buffer and window position.
     * <p>
     * The position reconstructed from the depth buffer in 2D may be slightly different from those
     * reconstructed in 3D and Columbus view. This is caused by the difference in the distribution
     * of depth values of perspective and orthographic projection.
     * </p>
     * <p>
     * Set {@link Scene#pickTranslucentDepth} to <code>true</code> to include the depth of
     * translucent primitives; otherwise, this essentially picks through translucent primitives.
     * </p>
     *
     * @param {Cartesian2} windowPosition Window coordinates to perform picking on.
     * @param {Cartesian3} [result] The object on which to restore the result.
     * @returns {Cartesian3} The cartesian position.
     *
     * @exception {DeveloperError} Picking from the depth buffer is not supported. Check pickPositionSupported.
     */
    Scene.prototype.pickPosition = function(windowPosition, result) {
        return this._picking.pickPosition(this, windowPosition, result);
    };

    /**
     * Returns a list of objects, each containing a `primitive` property, for all primitives at
     * a particular window coordinate position. Other properties may also be set depending on the
     * type of primitive and may be used to further identify the picked object. The primitives in
     * the list are ordered by their visual order in the scene (front to back).
     *
     * @param {Cartesian2} windowPosition Window coordinates to perform picking on.
     * @param {Number} [limit] If supplied, stop drilling after collecting this many picks.
     * @param {Number} [width=3] Width of the pick rectangle.
     * @param {Number} [height=3] Height of the pick rectangle.
     * @returns {Object[]} Array of objects, each containing 1 picked primitives.
     *
     * @exception {DeveloperError} windowPosition is undefined.
     *
     * @example
     * var pickedObjects = scene.drillPick(new Cesium.Cartesian2(100.0, 200.0));
     *
     * @see Scene#pick
     */
    Scene.prototype.drillPick = function(windowPosition, limit, width, height) {
        return this._picking.drillPick(this, windowPosition, limit, width, height);
    };

    function updatePreloadPass(scene) {
        var frameState = scene._frameState;
        preloadTilesetPassState.camera = frameState.camera;
        preloadTilesetPassState.cullingVolume = frameState.cullingVolume;

        var primitives = scene.primitives;
        primitives.updateForPass(frameState, preloadTilesetPassState);
    }

    function updatePreloadFlightPass(scene) {
        var frameState = scene._frameState;
        var camera = frameState.camera;
        if (!camera.canPreloadFlight()) {
            return;
        }

        preloadFlightTilesetPassState.camera = scene.preloadFlightCamera;
        preloadFlightTilesetPassState.cullingVolume = scene.preloadFlightCullingVolume;

        var primitives = scene.primitives;
        primitives.updateForPass(frameState, preloadFlightTilesetPassState);
    }

    function updateRequestRenderModeDeferCheckPass(scene) {
        // Check if any ignored requests are ready to go (to wake rendering up again)
        scene.primitives.updateForPass(scene._frameState, requestRenderModeDeferCheckPassState);
    }

    /**
     * Returns an object containing the first object intersected by the ray and the position of intersection,
     * or <code>undefined</code> if there were no intersections. The intersected object has a <code>primitive</code>
     * property that contains the intersected primitive. Other properties may be set depending on the type of primitive
     * and may be used to further identify the picked object. The ray must be given in world coordinates.
     * <p>
     * This function only picks globe tiles and 3D Tiles that are rendered in the current view. Picks all other
     * primitives regardless of their visibility.
     * </p>
     *
     * @private
     *
     * @param {Ray} ray The ray.
     * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection.
     * @param {Number} [width=0.1] Width of the intersection volume in meters.
     * @returns {Object} An object containing the object and position of the first intersection.
     *
     * @exception {DeveloperError} Ray intersections are only supported in 3D mode.
     */
    Scene.prototype.pickFromRay = function(ray, objectsToExclude, width) {
        return this._picking.pickFromRay(this, ray, objectsToExclude, width);
    };

    /**
     * Returns a list of objects, each containing the object intersected by the ray and the position of intersection.
     * The intersected object has a <code>primitive</code> property that contains the intersected primitive. Other
     * properties may also be set depending on the type of primitive and may be used to further identify the picked object.
     * The primitives in the list are ordered by first intersection to last intersection. The ray must be given in
     * world coordinates.
     * <p>
     * This function only picks globe tiles and 3D Tiles that are rendered in the current view. Picks all other
     * primitives regardless of their visibility.
     * </p>
     *
     * @private
     *
     * @param {Ray} ray The ray.
     * @param {Number} [limit=Number.MAX_VALUE] If supplied, stop finding intersections after this many intersections.
     * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection.
     * @param {Number} [width=0.1] Width of the intersection volume in meters.
     * @returns {Object[]} List of objects containing the object and position of each intersection.
     *
     * @exception {DeveloperError} Ray intersections are only supported in 3D mode.
     */
    Scene.prototype.drillPickFromRay = function(ray, limit, objectsToExclude, width) {
        return this._picking.drillPickFromRay(this, ray, limit, objectsToExclude, width);
    };

    /**
     * Initiates an asynchronous {@link Scene#pickFromRay} request using the maximum level of detail for 3D Tilesets
     * regardless of visibility.
     *
     * @private
     *
     * @param {Ray} ray The ray.
     * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection.
     * @param {Number} [width=0.1] Width of the intersection volume in meters.
     * @returns {Promise.<Object>} A promise that resolves to an object containing the object and position of the first intersection.
     *
     * @exception {DeveloperError} Ray intersections are only supported in 3D mode.
     */
    Scene.prototype.pickFromRayMostDetailed = function(ray, objectsToExclude, width) {
        return this._picking.pickFromRayMostDetailed(this, ray, objectsToExclude, width);
    };

    /**
     * Initiates an asynchronous {@link Scene#drillPickFromRay} request using the maximum level of detail for 3D Tilesets
     * regardless of visibility.
     *
     * @private
     *
     * @param {Ray} ray The ray.
     * @param {Number} [limit=Number.MAX_VALUE] If supplied, stop finding intersections after this many intersections.
     * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection.
     * @param {Number} [width=0.1] Width of the intersection volume in meters.
     * @returns {Promise.<Object[]>} A promise that resolves to a list of objects containing the object and position of each intersection.
     *
     * @exception {DeveloperError} Ray intersections are only supported in 3D mode.
     */
    Scene.prototype.drillPickFromRayMostDetailed = function(ray, limit, objectsToExclude, width) {
        return this._picking.drillPickFromRayMostDetailed(this, ray, limit, objectsToExclude, width);
    };

    /**
     * Returns the height of scene geometry at the given cartographic position or <code>undefined</code> if there was no
     * scene geometry to sample height from. The height of the input position is ignored. May be used to clamp objects to
     * the globe, 3D Tiles, or primitives in the scene.
     * <p>
     * This function only samples height from globe tiles and 3D Tiles that are rendered in the current view. Samples height
     * from all other primitives regardless of their visibility.
     * </p>
     *
     * @param {Cartographic} position The cartographic position to sample height from.
     * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not sample height from.
     * @param {Number} [width=0.1] Width of the intersection volume in meters.
     * @returns {Number} The height. This may be <code>undefined</code> if there was no scene geometry to sample height from.
     *
     * @example
     * var position = new Cesium.Cartographic(-1.31968, 0.698874);
     * var height = viewer.scene.sampleHeight(position);
     * console.log(height);
     *
     * @see Scene#clampToHeight
     * @see Scene#clampToHeightMostDetailed
     * @see Scene#sampleHeightMostDetailed
     *
     * @exception {DeveloperError} sampleHeight is only supported in 3D mode.
     * @exception {DeveloperError} sampleHeight requires depth texture support. Check sampleHeightSupported.
     */
    Scene.prototype.sampleHeight = function(position, objectsToExclude, width) {
        return this._picking.sampleHeight(this, position, objectsToExclude, width);
    };

    /**
     * Clamps the given cartesian position to the scene geometry along the geodetic surface normal. Returns the
     * clamped position or <code>undefined</code> if there was no scene geometry to clamp to. May be used to clamp
     * objects to the globe, 3D Tiles, or primitives in the scene.
     * <p>
     * This function only clamps to globe tiles and 3D Tiles that are rendered in the current view. Clamps to
     * all other primitives regardless of their visibility.
     * </p>
     *
     * @param {Cartesian3} cartesian The cartesian position.
     * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not clamp to.
     * @param {Number} [width=0.1] Width of the intersection volume in meters.
     * @param {Cartesian3} [result] An optional object to return the clamped position.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided. This may be <code>undefined</code> if there was no scene geometry to clamp to.
     *
     * @example
     * // Clamp an entity to the underlying scene geometry
     * var position = entity.position.getValue(Cesium.JulianDate.now());
     * entity.position = viewer.scene.clampToHeight(position);
     *
     * @see Scene#sampleHeight
     * @see Scene#sampleHeightMostDetailed
     * @see Scene#clampToHeightMostDetailed
     *
     * @exception {DeveloperError} clampToHeight is only supported in 3D mode.
     * @exception {DeveloperError} clampToHeight requires depth texture support. Check clampToHeightSupported.
     */
    Scene.prototype.clampToHeight = function(cartesian, objectsToExclude, width, result) {
        return this._picking.clampToHeight(this, cartesian, objectsToExclude, width, result);
    };

    /**
     * Initiates an asynchronous {@link Scene#sampleHeight} query for an array of {@link Cartographic} positions
     * using the maximum level of detail for 3D Tilesets in the scene. The height of the input positions is ignored.
     * Returns a promise that is resolved when the query completes. Each point height is modified in place.
     * If a height cannot be determined because no geometry can be sampled at that location, or another error occurs,
     * the height is set to undefined.
     *
     * @param {Cartographic[]} positions The cartographic positions to update with sampled heights.
     * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not sample height from.
     * @param {Number} [width=0.1] Width of the intersection volume in meters.
     * @returns {Promise.<Number[]>} A promise that resolves to the provided list of positions when the query has completed.
     *
     * @example
     * var positions = [
     *     new Cesium.Cartographic(-1.31968, 0.69887),
     *     new Cesium.Cartographic(-1.10489, 0.83923)
     * ];
     * var promise = viewer.scene.sampleHeightMostDetailed(positions);
     * promise.then(function(updatedPosition) {
     *     // positions[0].height and positions[1].height have been updated.
     *     // updatedPositions is just a reference to positions.
     * }
     *
     * @see Scene#sampleHeight
     *
     * @exception {DeveloperError} sampleHeightMostDetailed is only supported in 3D mode.
     * @exception {DeveloperError} sampleHeightMostDetailed requires depth texture support. Check sampleHeightSupported.
     */
    Scene.prototype.sampleHeightMostDetailed = function(positions, objectsToExclude, width) {
        return this._picking.sampleHeightMostDetailed(this, positions, objectsToExclude, width);
    };

    /**
     * Initiates an asynchronous {@link Scene#clampToHeight} query for an array of {@link Cartesian3} positions
     * using the maximum level of detail for 3D Tilesets in the scene. Returns a promise that is resolved when
     * the query completes. Each position is modified in place. If a position cannot be clamped because no geometry
     * can be sampled at that location, or another error occurs, the element in the array is set to undefined.
     *
     * @param {Cartesian3[]} cartesians The cartesian positions to update with clamped positions.
     * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not clamp to.
     * @param {Number} [width=0.1] Width of the intersection volume in meters.
     * @returns {Promise.<Cartesian3[]>} A promise that resolves to the provided list of positions when the query has completed.
     *
     * @example
     * var cartesians = [
     *     entities[0].position.getValue(Cesium.JulianDate.now()),
     *     entities[1].position.getValue(Cesium.JulianDate.now())
     * ];
     * var promise = viewer.scene.clampToHeightMostDetailed(cartesians);
     * promise.then(function(updatedCartesians) {
     *     entities[0].position = updatedCartesians[0];
     *     entities[1].position = updatedCartesians[1];
     * }
     *
     * @see Scene#clampToHeight
     *
     * @exception {DeveloperError} clampToHeightMostDetailed is only supported in 3D mode.
     * @exception {DeveloperError} clampToHeightMostDetailed requires depth texture support. Check clampToHeightSupported.
     */
    Scene.prototype.clampToHeightMostDetailed = function(cartesians, objectsToExclude, width) {
        return this._picking.clampToHeightMostDetailed(this, cartesians, objectsToExclude, width);
    };

    /**
     * Transforms a position in cartesian coordinates to canvas coordinates.  This is commonly used to place an
     * HTML element at the same screen position as an object in the scene.
     *
     * @param {Cartesian3} position The position in cartesian coordinates.
     * @param {Cartesian2} [result] An optional object to return the input position transformed to canvas coordinates.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.  This may be <code>undefined</code> if the input position is near the center of the ellipsoid.
     *
     * @example
     * // Output the canvas position of longitude/latitude (0, 0) every time the mouse moves.
     * var scene = widget.scene;
     * var ellipsoid = scene.globe.ellipsoid;
     * var position = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
     * var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
     * handler.setInputAction(function(movement) {
     *     console.log(scene.cartesianToCanvasCoordinates(position));
     * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
     */
    Scene.prototype.cartesianToCanvasCoordinates = function(position, result) {
        return SceneTransforms.wgs84ToWindowCoordinates(this, position, result);
    };

    /**
     * Instantly completes an active transition.
     */
    Scene.prototype.completeMorph = function(){
        this._transitioner.completeMorph();
    };

    /**
     * Asynchronously transitions the scene to 2D.
     * @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete.
     */
    Scene.prototype.morphTo2D = function(duration) {
        var ellipsoid;
        var globe = this.globe;
        if (defined(globe)) {
            ellipsoid = globe.ellipsoid;
        } else {
            ellipsoid = this.mapProjection.ellipsoid;
        }
        duration = defaultValue(duration, 2.0);
        this._transitioner.morphTo2D(duration, ellipsoid);
    };

    /**
     * Asynchronously transitions the scene to Columbus View.
     * @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete.
     */
    Scene.prototype.morphToColumbusView = function(duration) {
        var ellipsoid;
        var globe = this.globe;
        if (defined(globe)) {
            ellipsoid = globe.ellipsoid;
        } else {
            ellipsoid = this.mapProjection.ellipsoid;
        }
        duration = defaultValue(duration, 2.0);
        this._transitioner.morphToColumbusView(duration, ellipsoid);
    };

    /**
     * Asynchronously transitions the scene to 3D.
     * @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete.
     */
    Scene.prototype.morphTo3D = function(duration) {
        var ellipsoid;
        var globe = this.globe;
        if (defined(globe)) {
            ellipsoid = globe.ellipsoid;
        } else {
            ellipsoid = this.mapProjection.ellipsoid;
        }
        duration = defaultValue(duration, 2.0);
        this._transitioner.morphTo3D(duration, ellipsoid);
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see Scene#destroy
     */
    Scene.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * scene = scene && scene.destroy();
     *
     * @see Scene#isDestroyed
     */
    Scene.prototype.destroy = function() {
        this._tweens.removeAll();
        this._computeEngine = this._computeEngine && this._computeEngine.destroy();
        this._screenSpaceCameraController = this._screenSpaceCameraController && this._screenSpaceCameraController.destroy();
        this._deviceOrientationCameraController = this._deviceOrientationCameraController && !this._deviceOrientationCameraController.isDestroyed() && this._deviceOrientationCameraController.destroy();
        this._primitives = this._primitives && this._primitives.destroy();
        this._groundPrimitives = this._groundPrimitives && this._groundPrimitives.destroy();
        this._globe = this._globe && this._globe.destroy();
        this.skyBox = this.skyBox && this.skyBox.destroy();
        this.skyAtmosphere = this.skyAtmosphere && this.skyAtmosphere.destroy();
        this._debugSphere = this._debugSphere && this._debugSphere.destroy();
        this.sun = this.sun && this.sun.destroy();
        this._sunPostProcess = this._sunPostProcess && this._sunPostProcess.destroy();
        this._depthPlane = this._depthPlane && this._depthPlane.destroy();
        this._transitioner = this._transitioner && this._transitioner.destroy();
        this._debugFrustumPlanes = this._debugFrustumPlanes && this._debugFrustumPlanes.destroy();
        this._brdfLutGenerator = this._brdfLutGenerator && this._brdfLutGenerator.destroy();
        this._picking = this._picking && this._picking.destroy();

        this._defaultView = this._defaultView && this._defaultView.destroy();
        this._view = undefined;

        if (this._removeCreditContainer) {
            this._canvas.parentNode.removeChild(this._creditContainer);
        }

        this.postProcessStages = this.postProcessStages && this.postProcessStages.destroy();

        this._context = this._context && this._context.destroy();
        this._frameState.creditDisplay = this._frameState.creditDisplay && this._frameState.creditDisplay.destroy();

        if (defined(this._performanceDisplay)){
            this._performanceDisplay = this._performanceDisplay && this._performanceDisplay.destroy();
            this._performanceContainer.parentNode.removeChild(this._performanceContainer);
        }

        this._removeRequestListenerCallback();
        this._removeTaskProcessorListenerCallback();
        for (var i = 0; i < this._removeGlobeCallbacks.length; ++i) {
            this._removeGlobeCallbacks[i]();
        }
        this._removeGlobeCallbacks.length = 0;

        return destroyObject(this);
    };
export default Scene;
